mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Merged pipeline hygiene
- Lazy shore-band noise, zero interpolator groups dropped, inline prefill, dead streams removed
This commit is contained in:
commit
2471066937
13 files changed
+980
-126
No files matched your search
@@ -17,8 +17,6 @@ import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
|||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public class ChunkContext {
|
public class ChunkContext {
|
||||||
@@ -95,38 +93,11 @@ public class ChunkContext {
|
|||||||
if (hydrologyRuntime != null && (resolvedPlan.height || resolvedPlan.biome)) {
|
if (hydrologyRuntime != null && (resolvedPlan.height || resolvedPlan.biome)) {
|
||||||
hydrologyRuntime.prepareChunkColumns(x, z);
|
hydrologyRuntime.prepareChunkColumns(x, z);
|
||||||
}
|
}
|
||||||
List<Runnable> fillTasks = new ArrayList<>(6);
|
if (shouldPrefillAsync(resolvedPlan, complex.isPlatformMainThread())) {
|
||||||
if (resolvedPlan.height) {
|
prefillAsync(resolvedPlan);
|
||||||
fillTasks.add(() -> height.fillRounded(roundedHeight));
|
|
||||||
}
|
|
||||||
if (resolvedPlan.biome) {
|
|
||||||
fillTasks.add(new PrefillFillTask(biome));
|
|
||||||
}
|
|
||||||
if (resolvedPlan.rock) {
|
|
||||||
fillTasks.add(new PrefillFillTask(rock));
|
|
||||||
}
|
|
||||||
if (resolvedPlan.fluid) {
|
|
||||||
fillTasks.add(new PrefillFillTask(fluid));
|
|
||||||
}
|
|
||||||
if (resolvedPlan.region) {
|
|
||||||
fillTasks.add(new PrefillFillTask(region));
|
|
||||||
}
|
|
||||||
if (resolvedPlan.cave) {
|
|
||||||
fillTasks.add(new PrefillFillTask(cave));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldPrefillAsync(resolvedPlan, fillTasks.size())) {
|
|
||||||
for (Runnable fillTask : fillTasks) {
|
|
||||||
fillTask.run();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
List<CompletableFuture<Void>> futures = new ArrayList<>(fillTasks.size());
|
prefillSurface(resolvedPlan);
|
||||||
for (Runnable fillTask : fillTasks) {
|
prefillPalettes(resolvedPlan);
|
||||||
futures.add(CompletableFuture.runAsync(fillTask, MultiBurst.burst));
|
|
||||||
}
|
|
||||||
for (CompletableFuture<Void> future : futures) {
|
|
||||||
future.join();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (capturePrefillMetric) {
|
if (capturePrefillMetric) {
|
||||||
metrics.getContextPrefill().put((System.nanoTime() - totalStartNanos) / 1_000_000D);
|
metrics.getContextPrefill().put((System.nanoTime() - totalStartNanos) / 1_000_000D);
|
||||||
@@ -134,14 +105,46 @@ public class ChunkContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean shouldPrefillAsync(PrefillPlan prefillPlan, int fillTaskCount) {
|
static boolean shouldPrefillAsync(PrefillPlan prefillPlan, boolean platformMainThread) {
|
||||||
if (prefillPlan == PrefillPlan.NATURAL_TERRAIN || fillTaskCount <= 1 || !IrisPlatforms.isBound()) {
|
if (!platformMainThread || prefillPlan == PrefillPlan.NATURAL_TERRAIN || prefillPlan == PrefillPlan.NONE
|
||||||
|
|| !IrisPlatforms.isBound()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !MultiBurst.burst.ownsCurrentThread();
|
return !MultiBurst.burst.ownsCurrentThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void prefillSurface(PrefillPlan prefillPlan) {
|
||||||
|
if (prefillPlan.height) {
|
||||||
|
height.fillRounded(roundedHeight);
|
||||||
|
}
|
||||||
|
if (prefillPlan.biome) {
|
||||||
|
biome.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prefillPalettes(PrefillPlan prefillPlan) {
|
||||||
|
if (prefillPlan.rock) {
|
||||||
|
rock.fill();
|
||||||
|
}
|
||||||
|
if (prefillPlan.fluid) {
|
||||||
|
fluid.fill();
|
||||||
|
}
|
||||||
|
if (prefillPlan.region) {
|
||||||
|
region.fill();
|
||||||
|
}
|
||||||
|
if (prefillPlan.cave) {
|
||||||
|
cave.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prefillAsync(PrefillPlan prefillPlan) {
|
||||||
|
CompletableFuture<Void> surface = CompletableFuture.runAsync(() -> prefillSurface(prefillPlan), MultiBurst.burst);
|
||||||
|
CompletableFuture<Void> palettes = CompletableFuture.runAsync(() -> prefillPalettes(prefillPlan), MultiBurst.burst);
|
||||||
|
surface.join();
|
||||||
|
palettes.join();
|
||||||
|
}
|
||||||
|
|
||||||
public void setTerrainBiomeOutput(Hunk<NativeBiome> terrainBiomes) {
|
public void setTerrainBiomeOutput(Hunk<NativeBiome> terrainBiomes) {
|
||||||
this.terrainBiomes = terrainBiomes;
|
this.terrainBiomes = terrainBiomes;
|
||||||
}
|
}
|
||||||
@@ -295,17 +298,4 @@ public class ChunkContext {
|
|||||||
|
|
||||||
public record TransitionTerrainColumn(Terrain3DColumn solids, int fluidHeight) {
|
public record TransitionTerrainColumn(Terrain3DColumn solids, int fluidHeight) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class PrefillFillTask implements Runnable {
|
|
||||||
private final ChunkedDataCache<?> dataCache;
|
|
||||||
|
|
||||||
private PrefillFillTask(ChunkedDataCache<?> dataCache) {
|
|
||||||
this.dataCache = dataCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
dataCache.fill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,6 @@ import java.awt.Color;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
@@ -224,10 +223,6 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
|
|
||||||
EngineWorldManager getWorldManager();
|
EngineWorldManager getWorldManager();
|
||||||
|
|
||||||
default UUID getBiomeID(int x, int z) {
|
|
||||||
return getComplex().getBaseBiomeIDStream().get(x, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getParallelism();
|
int getParallelism();
|
||||||
|
|
||||||
void setParallelism(int parallelism);
|
void setParallelism(int parallelism);
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import art.arcane.iris.generation.subterrain.SubterrainRasterizer;
|
|||||||
import art.arcane.iris.configuration.IrisSettings;
|
import art.arcane.iris.configuration.IrisSettings;
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
import art.arcane.iris.pack.loading.IrisRegistrant;
|
import art.arcane.iris.pack.loading.IrisRegistrant;
|
||||||
import art.arcane.iris.generation.cache.Cache;
|
|
||||||
import art.arcane.iris.studio.generation.BiomeBuffetLayout;
|
import art.arcane.iris.studio.generation.BiomeBuffetLayout;
|
||||||
import art.arcane.iris.world.history.GenerationBlend;
|
import art.arcane.iris.world.history.GenerationBlend;
|
||||||
import art.arcane.iris.world.history.TransitionGenerationPlan;
|
import art.arcane.iris.world.history.TransitionGenerationPlan;
|
||||||
@@ -52,8 +51,6 @@ import art.arcane.iris.generation.terrain.InferredType;
|
|||||||
import art.arcane.iris.generation.biome.IrisBiome;
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
import art.arcane.iris.generation.biome.IrisBiomeGeneratorLink;
|
import art.arcane.iris.generation.biome.IrisBiomeGeneratorLink;
|
||||||
import art.arcane.iris.pack.value.IrisRaritySelection;
|
import art.arcane.iris.pack.value.IrisRaritySelection;
|
||||||
import art.arcane.iris.generation.decoration.IrisDecorationPart;
|
|
||||||
import art.arcane.iris.generation.decoration.IrisDecorator;
|
|
||||||
import art.arcane.iris.generation.noise.IrisGenerator;
|
import art.arcane.iris.generation.noise.IrisGenerator;
|
||||||
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||||
import art.arcane.iris.generation.terrain.ContinentGenerator;
|
import art.arcane.iris.generation.terrain.ContinentGenerator;
|
||||||
@@ -80,12 +77,12 @@ import art.arcane.iris.generation.terrain.ProceduralTerrainHeightSampler;
|
|||||||
import art.arcane.iris.generation.terrain.HydrologyBankTerrainRuntime;
|
import art.arcane.iris.generation.terrain.HydrologyBankTerrainRuntime;
|
||||||
import art.arcane.iris.spi.IrisPlatforms;
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.iris.generation.block.DataProvider;
|
import art.arcane.iris.generation.block.DataProvider;
|
||||||
import art.arcane.volmlib.util.math.M;
|
import art.arcane.volmlib.util.math.M;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import art.arcane.volmlib.util.interpolation.IrisInterpolation;
|
||||||
import art.arcane.volmlib.util.interpolation.NoiseBounds;
|
import art.arcane.volmlib.util.interpolation.NoiseBounds;
|
||||||
import art.arcane.volmlib.util.interpolation.NoiseBoundsProvider;
|
import art.arcane.volmlib.util.interpolation.NoiseBoundsProvider;
|
||||||
import art.arcane.volmlib.util.noise.CNG;
|
import art.arcane.volmlib.util.noise.CNG;
|
||||||
@@ -99,8 +96,10 @@ import lombok.Setter;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
@@ -133,6 +132,8 @@ public class IrisComplex implements DataProvider {
|
|||||||
private static final double SLOPE_RUN = 3D;
|
private static final double SLOPE_RUN = 3D;
|
||||||
/** A sheer coast would let shoreMinimumWidth buy unbounded height, so the shore band stops climbing here. */
|
/** A sheer coast would let shoreMinimumWidth buy unbounded height, so the shore band stops climbing here. */
|
||||||
private static final double MAX_SHORE_BAND_GRADIENT = 3D;
|
private static final double MAX_SHORE_BAND_GRADIENT = 3D;
|
||||||
|
static final double SHORE_NOISE_LOWEST = -0.5D;
|
||||||
|
static final double SHORE_NOISE_HIGHEST = 1.5D;
|
||||||
private static final Comparator<IrisInterpolator> INTERPOLATOR_ORDER = Comparator
|
private static final Comparator<IrisInterpolator> INTERPOLATOR_ORDER = Comparator
|
||||||
.comparing((IrisInterpolator interpolator) -> interpolator.getFunction().name())
|
.comparing((IrisInterpolator interpolator) -> interpolator.getFunction().name())
|
||||||
.thenComparingDouble(IrisInterpolator::getHorizontalScale);
|
.thenComparingDouble(IrisInterpolator::getHorizontalScale);
|
||||||
@@ -176,7 +177,6 @@ public class IrisComplex implements DataProvider {
|
|||||||
private ProceduralStream<IrisRegion> regionStream;
|
private ProceduralStream<IrisRegion> regionStream;
|
||||||
private ProceduralStream<Double> regionStyleStream;
|
private ProceduralStream<Double> regionStyleStream;
|
||||||
private ProceduralStream<Double> regionIdentityStream;
|
private ProceduralStream<Double> regionIdentityStream;
|
||||||
private ProceduralStream<UUID> regionIDStream;
|
|
||||||
private ProceduralStream<InferredType> bridgeStream;
|
private ProceduralStream<InferredType> bridgeStream;
|
||||||
private ProceduralStream<IrisBiome> landBiomeStream;
|
private ProceduralStream<IrisBiome> landBiomeStream;
|
||||||
private ProceduralStream<IrisBiome> caveBiomeStream;
|
private ProceduralStream<IrisBiome> caveBiomeStream;
|
||||||
@@ -185,10 +185,8 @@ public class IrisComplex implements DataProvider {
|
|||||||
private ProceduralStream<IrisBiome> baseBiomeStream;
|
private ProceduralStream<IrisBiome> baseBiomeStream;
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private final transient boolean reuseNaturalBaseBiome;
|
private final transient boolean reuseNaturalBaseBiome;
|
||||||
private ProceduralStream<UUID> baseBiomeIDStream;
|
|
||||||
private ProceduralStream<IrisBiome> naturalTrueBiomeStream;
|
private ProceduralStream<IrisBiome> naturalTrueBiomeStream;
|
||||||
private ProceduralStream<IrisBiome> trueBiomeStream;
|
private ProceduralStream<IrisBiome> trueBiomeStream;
|
||||||
private ProceduralStream<NativeBiome> trueBiomeDerivativeStream;
|
|
||||||
private ProceduralStream<Double> naturalHeightStream;
|
private ProceduralStream<Double> naturalHeightStream;
|
||||||
private ProceduralStream<Double> baseTerrainHeightStream;
|
private ProceduralStream<Double> baseTerrainHeightStream;
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@@ -230,13 +228,6 @@ public class IrisComplex implements DataProvider {
|
|||||||
private ProceduralStream<Double> riverCarveWeightStream;
|
private ProceduralStream<Double> riverCarveWeightStream;
|
||||||
private ProceduralStream<Double> riverWaterSurfaceStream;
|
private ProceduralStream<Double> riverWaterSurfaceStream;
|
||||||
private ProceduralStream<Integer> topSurfaceStream;
|
private ProceduralStream<Integer> topSurfaceStream;
|
||||||
private ProceduralStream<IrisDecorator> terrainSurfaceDecoration;
|
|
||||||
private ProceduralStream<IrisDecorator> terrainCeilingDecoration;
|
|
||||||
private ProceduralStream<IrisDecorator> terrainCaveSurfaceDecoration;
|
|
||||||
private ProceduralStream<IrisDecorator> terrainCaveCeilingDecoration;
|
|
||||||
private ProceduralStream<IrisDecorator> seaSurfaceDecoration;
|
|
||||||
private ProceduralStream<IrisDecorator> seaFloorDecoration;
|
|
||||||
private ProceduralStream<IrisDecorator> shoreSurfaceDecoration;
|
|
||||||
private ProceduralStream<NativeBlockState> rockStream;
|
private ProceduralStream<NativeBlockState> rockStream;
|
||||||
private ProceduralStream<NativeBlockState> fluidStream;
|
private ProceduralStream<NativeBlockState> fluidStream;
|
||||||
private Map<String, ProceduralStream<NativeBlockState>> hydrologyFluidStreams;
|
private Map<String, ProceduralStream<NativeBlockState>> hydrologyFluidStreams;
|
||||||
@@ -350,7 +341,9 @@ public class IrisComplex implements DataProvider {
|
|||||||
preparedRegions,
|
preparedRegions,
|
||||||
(region, inferredType) -> compileInferredBiomeStream(engine, region, inferredType, emptyBiome)
|
(region, inferredType) -> compileInferredBiomeStream(engine, region, inferredType, emptyBiome)
|
||||||
);
|
);
|
||||||
GeneratorGroup[] generatorGroups = freezeGeneratorGroups(generators);
|
GeneratorGroup[] generatorGroups = focusBiome == null && !buffetActive
|
||||||
|
? contributingGroups(freezeGeneratorGroups(generators), baseCapableBiomes(preparedRegions))
|
||||||
|
: freezeGeneratorGroups(generators);
|
||||||
int interpolatorCount = generatorGroups.length;
|
int interpolatorCount = generatorGroups.length;
|
||||||
frozenInterpolators = new IrisInterpolator[interpolatorCount];
|
frozenInterpolators = new IrisInterpolator[interpolatorCount];
|
||||||
frozenGenerators = new IrisGenerator[interpolatorCount][];
|
frozenGenerators = new IrisGenerator[interpolatorCount][];
|
||||||
@@ -384,8 +377,6 @@ public class IrisComplex implements DataProvider {
|
|||||||
: GenerationStreams.cache2D(RegionSelection.select(new RegionSelection.Options(
|
: GenerationStreams.cache2D(RegionSelection.select(new RegionSelection.Options(
|
||||||
engine.getDimension(), data, regionStyleStream, imageMapRuntime, null)),
|
engine.getDimension(), data, regionStyleStream, imageMapRuntime, null)),
|
||||||
"regionStream", engine, cacheSize);
|
"regionStream", engine, cacheSize);
|
||||||
regionIDStream = regionIdentityStream.convertCached((i) -> new UUID(Double.doubleToLongBits(i),
|
|
||||||
String.valueOf(i * 38445).hashCode() * 3245556666L));
|
|
||||||
caveBiomeStream = GenerationStreams.cache2D(GenerationStreams.contextInjecting(regionStream, engine, (c, x, z) -> c.getRegion().get(x, z))
|
caveBiomeStream = GenerationStreams.cache2D(GenerationStreams.contextInjecting(regionStream, engine, (c, x, z) -> c.getRegion().get(x, z))
|
||||||
.convert((r) -> createInferredBiomeStream(r, InferredType.CAVE))
|
.convert((r) -> createInferredBiomeStream(r, InferredType.CAVE))
|
||||||
.convertAware2D(ProceduralStream::get), "caveBiomeStream", engine, cacheSize);
|
.convertAware2D(ProceduralStream::get), "caveBiomeStream", engine, cacheSize);
|
||||||
@@ -487,8 +478,6 @@ public class IrisComplex implements DataProvider {
|
|||||||
shoreSlopeStream = terrain3DEnabled ? baseTerrainHeightStream.slope(3) : naturalSlopeStream;
|
shoreSlopeStream = terrain3DEnabled ? baseTerrainHeightStream.slope(3) : naturalSlopeStream;
|
||||||
trueBiomeStream = focusedBiomes != null ? GenerationStreams.cache2D(focusedBiomes, "trueBiomeStream-focus", engine, cacheSize) : GenerationStreams.cache2D(heightStream
|
trueBiomeStream = focusedBiomes != null ? GenerationStreams.cache2D(focusedBiomes, "trueBiomeStream-focus", engine, cacheSize) : GenerationStreams.cache2D(heightStream
|
||||||
.convertAware2D((terrainHeight, x, z) -> resolveHydrologySurfaceBiome(terrainHeight, x, z)), "trueBiomeStream", engine, cacheSize);
|
.convertAware2D((terrainHeight, x, z) -> resolveHydrologySurfaceBiome(terrainHeight, x, z)), "trueBiomeStream", engine, cacheSize);
|
||||||
trueBiomeDerivativeStream = GenerationStreams.cache2D(GenerationStreams.contextInjecting(trueBiomeStream, engine, (c, x, z) -> c.getBiome().get(x, z))
|
|
||||||
.convert((b) -> IrisPlatforms.get().registries().biome(b.getDerivativeKey())), "trueBiomeDerivativeStream", engine, cacheSize);
|
|
||||||
riverDistanceStream = GenerationStreams.cache2DDouble(ProceduralStream.ofDouble(this::resolveHydrologyDistance), "riverDistanceStream", engine, cacheSize);
|
riverDistanceStream = GenerationStreams.cache2DDouble(ProceduralStream.ofDouble(this::resolveHydrologyDistance), "riverDistanceStream", engine, cacheSize);
|
||||||
riverFlowStream = GenerationStreams.cache2DDouble(ProceduralStream.ofDouble(this::resolveHydrologyFlow), "riverFlowStream", engine, cacheSize);
|
riverFlowStream = GenerationStreams.cache2DDouble(ProceduralStream.ofDouble(this::resolveHydrologyFlow), "riverFlowStream", engine, cacheSize);
|
||||||
riverCarveWeightStream = GenerationStreams.cache2DDouble(ProceduralStream.ofDouble(this::resolveHydrologyCarveWeight), "riverCarveWeightStream", engine, cacheSize);
|
riverCarveWeightStream = GenerationStreams.cache2DDouble(ProceduralStream.ofDouble(this::resolveHydrologyCarveWeight), "riverCarveWeightStream", engine, cacheSize);
|
||||||
@@ -498,26 +487,6 @@ public class IrisComplex implements DataProvider {
|
|||||||
riverWaterSurfaceStream.get(x, z)
|
riverWaterSurfaceStream.get(x, z)
|
||||||
)), "heightFluidStream", engine, cacheSize);
|
)), "heightFluidStream", engine, cacheSize);
|
||||||
maxHeightStream = ProceduralStream.ofDouble((x, z) -> height);
|
maxHeightStream = ProceduralStream.ofDouble((x, z) -> height);
|
||||||
terrainSurfaceDecoration = GenerationStreams.cache2D(GenerationStreams.contextInjecting(trueBiomeStream, engine, (c, x, z) -> c.getBiome().get(x, z))
|
|
||||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)), "terrainSurfaceDecoration", engine, cacheSize);
|
|
||||||
terrainCeilingDecoration = GenerationStreams.cache2D(GenerationStreams.contextInjecting(trueBiomeStream, engine, (c, x, z) -> c.getBiome().get(x, z))
|
|
||||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)), "terrainCeilingDecoration", engine, cacheSize);
|
|
||||||
terrainCaveSurfaceDecoration = GenerationStreams.cache2D(GenerationStreams.contextInjecting(caveBiomeStream, engine, (c, x, z) -> c.getCave().get(x, z))
|
|
||||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)), "terrainCaveSurfaceDecoration", engine, cacheSize);
|
|
||||||
terrainCaveCeilingDecoration = GenerationStreams.cache2D(GenerationStreams.contextInjecting(caveBiomeStream, engine, (c, x, z) -> c.getCave().get(x, z))
|
|
||||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)), "terrainCaveCeilingDecoration", engine, cacheSize);
|
|
||||||
shoreSurfaceDecoration = GenerationStreams.cache2D(GenerationStreams.contextInjecting(trueBiomeStream, engine, (c, x, z) -> c.getBiome().get(x, z))
|
|
||||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SHORE_LINE)), "shoreSurfaceDecoration", engine, cacheSize);
|
|
||||||
seaSurfaceDecoration = GenerationStreams.cache2D(GenerationStreams.contextInjecting(trueBiomeStream, engine, (c, x, z) -> c.getBiome().get(x, z))
|
|
||||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_SURFACE)), "seaSurfaceDecoration", engine, cacheSize);
|
|
||||||
seaFloorDecoration = GenerationStreams.cache2D(GenerationStreams.contextInjecting(trueBiomeStream, engine, (c, x, z) -> c.getBiome().get(x, z))
|
|
||||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_FLOOR)), "seaFloorDecoration", engine, cacheSize);
|
|
||||||
baseBiomeIDStream = GenerationStreams.cache2D(GenerationStreams.contextInjecting(trueBiomeStream, engine, (c, x, z) -> c.getBiome().get(x, z))
|
|
||||||
.convertAware2D((b, x, z) -> {
|
|
||||||
UUID d = regionIDStream.get(x, z);
|
|
||||||
return new UUID(b.getLoadKey().hashCode() * 818223L,
|
|
||||||
d.hashCode());
|
|
||||||
}), "", engine, cacheSize);
|
|
||||||
//@done
|
//@done
|
||||||
TerrainTransformer transformer = engine.getDimension().getTerrainTransform() == null ? null
|
TerrainTransformer transformer = engine.getDimension().getTerrainTransform() == null ? null
|
||||||
: TerrainTransformRegistry.discover().resolve(engine.getDimension().getTerrainTransform());
|
: TerrainTransformRegistry.discover().resolve(engine.getDimension().getTerrainTransform());
|
||||||
@@ -1415,6 +1384,10 @@ public class IrisComplex implements DataProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPlatformMainThread() {
|
||||||
|
return terrainEngine.getPlatformHooks().isMainThread();
|
||||||
|
}
|
||||||
|
|
||||||
public ProceduralStream<Double> getRawHeightStream() {
|
public ProceduralStream<Double> getRawHeightStream() {
|
||||||
return heightStream;
|
return heightStream;
|
||||||
}
|
}
|
||||||
@@ -1629,24 +1602,6 @@ public class IrisComplex implements DataProvider {
|
|||||||
return proceduralTerrainHeight.sample(worldX, worldZ);
|
return proceduralTerrainHeight.sample(worldX, worldZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IrisDecorator decorateFor(IrisBiome b, double x, double z, IrisDecorationPart part) {
|
|
||||||
RNG rngc = new RNG(Cache.key(((int) x), ((int) z)));
|
|
||||||
|
|
||||||
for (IrisDecorator i : b.getDecorators()) {
|
|
||||||
if (!i.getPartOf().equals(part)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBlockState block = i.getBlockData(b, rngc, x, z, data);
|
|
||||||
|
|
||||||
if (block != null) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IrisBiome resolveHydrologySurfaceBiome(double terrainHeight, double x, double z) {
|
private IrisBiome resolveHydrologySurfaceBiome(double terrainHeight, double x, double z) {
|
||||||
HydrologyColumnSample sample = hydrologySample(x, z);
|
HydrologyColumnSample sample = hydrologySample(x, z);
|
||||||
HydrologyColumnLayer layer = sample == null ? null : sample.primarySurfaceLayer().orElse(null);
|
HydrologyColumnLayer layer = sample == null ? null : sample.primarySurfaceLayer().orElse(null);
|
||||||
@@ -1730,6 +1685,33 @@ public class IrisComplex implements DataProvider {
|
|||||||
return bandTop + shoreMinimumWidth * Math.min(shoreSlopes.getDouble(x, z) / SLOPE_RUN, MAX_SHORE_BAND_GRADIENT);
|
return bandTop + shoreMinimumWidth * Math.min(shoreSlopes.getDouble(x, z) / SLOPE_RUN, MAX_SHORE_BAND_GRADIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean aboveShoreBand(
|
||||||
|
double height,
|
||||||
|
IrisRegion region,
|
||||||
|
double x,
|
||||||
|
double z,
|
||||||
|
double fluidHeight,
|
||||||
|
ProceduralStream<Double> shoreSlopes
|
||||||
|
) {
|
||||||
|
if (region.getShoreMinimumWidth() <= 0) {
|
||||||
|
double minimum = region.getShoreHeightMin();
|
||||||
|
double maximum = region.getShoreHeightMax();
|
||||||
|
double lowestTop = fluidHeight + shoreHeightAt(minimum, maximum, SHORE_NOISE_LOWEST);
|
||||||
|
double highestTop = fluidHeight + shoreHeightAt(minimum, maximum, SHORE_NOISE_HIGHEST);
|
||||||
|
if (height > Math.max(lowestTop, highestTop)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (height <= Math.min(lowestTop, highestTop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return height > shoreBandTop(height, region, x, z, fluidHeight, shoreSlopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double shoreHeightAt(double minimum, double maximum, double noise) {
|
||||||
|
return minimum == maximum ? minimum : IrisInterpolation.lerp(minimum, maximum, noise);
|
||||||
|
}
|
||||||
|
|
||||||
static IrisBiome resolveSurfaceBiome(
|
static IrisBiome resolveSurfaceBiome(
|
||||||
double height,
|
double height,
|
||||||
IrisBiome biome,
|
IrisBiome biome,
|
||||||
@@ -1745,13 +1727,15 @@ public class IrisComplex implements DataProvider {
|
|||||||
if (biome == null || region == null) {
|
if (biome == null || region == null) {
|
||||||
return biome;
|
return biome;
|
||||||
}
|
}
|
||||||
double shoreTop = shoreBandTop(height, region, x, z, fluidHeight, shoreSlopes);
|
|
||||||
|
|
||||||
if (height >= fluidHeight - 1 && height <= shoreTop && !biome.isShore()) {
|
if (height >= fluidHeight - 1 && !biome.isShore()) {
|
||||||
return shoreBiomes.get(x, z);
|
if (!aboveShoreBand(height, region, x, z, fluidHeight, shoreSlopes)) {
|
||||||
}
|
return shoreBiomes.get(x, z);
|
||||||
|
}
|
||||||
if (height > shoreTop && !biome.isLand()) {
|
if (!biome.isLand()) {
|
||||||
|
return landBiomes.get(x, z);
|
||||||
|
}
|
||||||
|
} else if (!biome.isLand() && aboveShoreBand(height, region, x, z, fluidHeight, shoreSlopes)) {
|
||||||
return landBiomes.get(x, z);
|
return landBiomes.get(x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1911,13 +1895,92 @@ public class IrisComplex implements DataProvider {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int grid = biomeBoundsSamplingStep;
|
||||||
|
GridBoundsCache cache = gridBoundsCache.get();
|
||||||
|
if (!ChunkCorners.supports(grid) || cache.cornersInUse) {
|
||||||
|
for (int interpolatorIndex = 0; interpolatorIndex < frozenInterpolators.length; interpolatorIndex++) {
|
||||||
|
h += interpolateGenerators(engine, frozenInterpolators[interpolatorIndex], interpolatorIndex, frozenGenerators[interpolatorIndex], x, z, seed);
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.cornersInUse = true;
|
||||||
|
try {
|
||||||
|
return cornerBlockHeight(cache, engine, grid, x, z, seed);
|
||||||
|
} finally {
|
||||||
|
cache.cornersInUse = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double cornerBlockHeight(GridBoundsCache cache, Engine engine, int grid, double x, double z, long seed) {
|
||||||
|
double h = 0;
|
||||||
|
int xi = (int) Math.floor(x);
|
||||||
|
int zi = (int) Math.floor(z);
|
||||||
|
int gx = xi & ~(grid - 1);
|
||||||
|
int gz = zi & ~(grid - 1);
|
||||||
|
double fx = (x - gx) / grid;
|
||||||
|
double fz = (z - gz) / grid;
|
||||||
|
ChunkCorners corners = cache.chunkCorners(xi >> 4, zi >> 4, frozenInterpolators.length, grid);
|
||||||
|
int cornerX = (gx - corners.originX) / grid;
|
||||||
|
int cornerZ = (gz - corners.originZ) / grid;
|
||||||
|
int cell = cornerZ * corners.cellsPerAxis + cornerX;
|
||||||
|
long heightSeed = seed + 239945;
|
||||||
for (int interpolatorIndex = 0; interpolatorIndex < frozenInterpolators.length; interpolatorIndex++) {
|
for (int interpolatorIndex = 0; interpolatorIndex < frozenInterpolators.length; interpolatorIndex++) {
|
||||||
h += interpolateGenerators(engine, frozenInterpolators[interpolatorIndex], interpolatorIndex, frozenGenerators[interpolatorIndex], x, z, seed);
|
IrisGenerator[] generators = frozenGenerators[interpolatorIndex];
|
||||||
|
if (generators.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int state = corners.cellState(interpolatorIndex, cell);
|
||||||
|
if (state == ChunkCorners.SILENT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long b00 = cornerAt(cache, engine, corners, interpolatorIndex, cornerX, cornerZ);
|
||||||
|
long b10 = fx == 0D ? 0L : cornerAt(cache, engine, corners, interpolatorIndex, cornerX + 1, cornerZ);
|
||||||
|
long b01 = fz == 0D ? 0L : cornerAt(cache, engine, corners, interpolatorIndex, cornerX, cornerZ + 1);
|
||||||
|
long b11 = fx == 0D || fz == 0D ? 0L : cornerAt(cache, engine, corners, interpolatorIndex, cornerX + 1, cornerZ + 1);
|
||||||
|
if (state == ChunkCorners.UNKNOWN) {
|
||||||
|
corners.classify(interpolatorIndex, cornerX, cornerZ, cell);
|
||||||
|
if (silentBounds(b00) && silentBounds(b10) && silentBounds(b01) && silentBounds(b11)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double lo;
|
||||||
|
double hi;
|
||||||
|
if (fx == 0D && fz == 0D) {
|
||||||
|
lo = boundsLow(b00);
|
||||||
|
hi = boundsHigh(b00);
|
||||||
|
} else if (fz == 0D) {
|
||||||
|
lo = biLerp(boundsLow(b00), boundsLow(b10), boundsLow(b00), boundsLow(b10), fx, fz);
|
||||||
|
hi = biLerp(boundsHigh(b00), boundsHigh(b10), boundsHigh(b00), boundsHigh(b10), fx, fz);
|
||||||
|
} else if (fx == 0D) {
|
||||||
|
lo = biLerp(boundsLow(b00), boundsLow(b00), boundsLow(b01), boundsLow(b01), fx, fz);
|
||||||
|
hi = biLerp(boundsHigh(b00), boundsHigh(b00), boundsHigh(b01), boundsHigh(b01), fx, fz);
|
||||||
|
} else {
|
||||||
|
lo = biLerp(boundsLow(b00), boundsLow(b10), boundsLow(b01), boundsLow(b11), fx, fz);
|
||||||
|
hi = biLerp(boundsHigh(b00), boundsHigh(b10), boundsHigh(b01), boundsHigh(b11), fx, fz);
|
||||||
|
}
|
||||||
|
h += averageGeneratorHeights(generators, lo, hi, x, z, heightSeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long cornerAt(GridBoundsCache cache, Engine engine, ChunkCorners corners, int interpolatorIndex, int column, int row) {
|
||||||
|
if (corners.known(interpolatorIndex, column, row)) {
|
||||||
|
return corners.corner(interpolatorIndex, column, row);
|
||||||
|
}
|
||||||
|
long packed = cornerBounds(cache, engine, frozenInterpolators[interpolatorIndex], interpolatorIndex,
|
||||||
|
frozenGenerators[interpolatorIndex], corners.originX + column * corners.grid, corners.originZ + row * corners.grid);
|
||||||
|
if (Double.isFinite(boundsLow(packed)) && Double.isFinite(boundsHigh(packed))) {
|
||||||
|
corners.remember(interpolatorIndex, column, row, packed);
|
||||||
|
}
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean silentBounds(long packed) {
|
||||||
|
return (packed & 0x7FFFFFFF7FFFFFFFL) == 0L;
|
||||||
|
}
|
||||||
|
|
||||||
private NoiseBounds naturalHeightBounds(
|
private NoiseBounds naturalHeightBounds(
|
||||||
Engine engine,
|
Engine engine,
|
||||||
KList<IrisShapedGeneratorStyle> overlayNoise,
|
KList<IrisShapedGeneratorStyle> overlayNoise,
|
||||||
@@ -2058,6 +2121,48 @@ public class IrisComplex implements DataProvider {
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GeneratorGroup[] contributingGroups(GeneratorGroup[] groups, Collection<IrisBiome> baseBiomes) {
|
||||||
|
List<GeneratorGroup> contributing = new ArrayList<>(groups.length);
|
||||||
|
for (GeneratorGroup group : groups) {
|
||||||
|
if (contributesHeight(group.generators(), baseBiomes)) {
|
||||||
|
contributing.add(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contributing.toArray(new GeneratorGroup[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean contributesHeight(IrisGenerator[] generators, Collection<IrisBiome> baseBiomes) {
|
||||||
|
for (IrisBiome biome : baseBiomes) {
|
||||||
|
GeneratorBounds bounds = computeGeneratorBounds(generators, biome);
|
||||||
|
if (bounds.min != 0D || bounds.max != 0D) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<IrisBiome> baseCapableBiomes(KList<IrisRegion> regions) {
|
||||||
|
Set<IrisBiome> reached = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||||
|
ArrayDeque<IrisBiome> pending = new ArrayDeque<>();
|
||||||
|
for (IrisRegion region : regions) {
|
||||||
|
pending.addAll(loadInferredBiomes(region.getLandBiomes(), InferredType.LAND));
|
||||||
|
pending.addAll(loadInferredBiomes(region.getSeaBiomes(), InferredType.SEA));
|
||||||
|
}
|
||||||
|
pending.addAll(imageMapRuntime.getMappedBiomes());
|
||||||
|
while (!pending.isEmpty()) {
|
||||||
|
IrisBiome biome = pending.poll();
|
||||||
|
if (!reached.add(biome)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (IrisBiome child : biome.getRealChildren(this)) {
|
||||||
|
if (child != null) {
|
||||||
|
pending.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reached;
|
||||||
|
}
|
||||||
|
|
||||||
static double averageGeneratorHeights(
|
static double averageGeneratorHeights(
|
||||||
IrisGenerator[] generators,
|
IrisGenerator[] generators,
|
||||||
double low,
|
double low,
|
||||||
@@ -2103,7 +2208,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
for (int i = 0; i < frozenInterpolators.length; i++) {
|
for (int i = 0; i < frozenInterpolators.length; i++) {
|
||||||
IdentityHashMap<IrisBiome, GeneratorBounds> interpolatorBounds = new IdentityHashMap<>(Math.max(allBiomes.size(), 16));
|
IdentityHashMap<IrisBiome, GeneratorBounds> interpolatorBounds = new IdentityHashMap<>(Math.max(allBiomes.size(), 16));
|
||||||
for (IrisBiome biome : allBiomes) {
|
for (IrisBiome biome : allBiomes) {
|
||||||
interpolatorBounds.put(biome, computeGeneratorBounds(engine, frozenGenerators[i], biome));
|
interpolatorBounds.put(biome, computeGeneratorBounds(frozenGenerators[i], biome));
|
||||||
}
|
}
|
||||||
bounds.put(frozenInterpolators[i], interpolatorBounds);
|
bounds.put(frozenInterpolators[i], interpolatorBounds);
|
||||||
}
|
}
|
||||||
@@ -2111,7 +2216,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GeneratorBounds computeGeneratorBounds(Engine engine, IrisGenerator[] generators, IrisBiome biome) {
|
private static GeneratorBounds computeGeneratorBounds(IrisGenerator[] generators, IrisBiome biome) {
|
||||||
double min = 0D;
|
double min = 0D;
|
||||||
double max = 0D;
|
double max = 0D;
|
||||||
|
|
||||||
@@ -2145,7 +2250,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneratorBounds computed = computeGeneratorBounds(engine, generators, biome);
|
GeneratorBounds computed = computeGeneratorBounds(generators, biome);
|
||||||
localBounds.put(biome, computed);
|
localBounds.put(biome, computed);
|
||||||
return computed;
|
return computed;
|
||||||
}
|
}
|
||||||
@@ -2246,6 +2351,27 @@ public class IrisComplex implements DataProvider {
|
|||||||
private final long[] packed = new long[GRID_BOUNDS_CACHE_SIZE];
|
private final long[] packed = new long[GRID_BOUNDS_CACHE_SIZE];
|
||||||
private final boolean[] valid = new boolean[GRID_BOUNDS_CACHE_SIZE];
|
private final boolean[] valid = new boolean[GRID_BOUNDS_CACHE_SIZE];
|
||||||
private final BoundsSampler sampler = new BoundsSampler();
|
private final BoundsSampler sampler = new BoundsSampler();
|
||||||
|
private ChunkCorners recentCorners;
|
||||||
|
private ChunkCorners previousCorners;
|
||||||
|
private boolean cornersInUse;
|
||||||
|
|
||||||
|
private ChunkCorners chunkCorners(int chunkX, int chunkZ, int groups, int grid) {
|
||||||
|
ChunkCorners recent = recentCorners;
|
||||||
|
if (recent != null && recent.matches(chunkX, chunkZ, groups, grid)) {
|
||||||
|
return recent;
|
||||||
|
}
|
||||||
|
ChunkCorners previous = previousCorners;
|
||||||
|
if (previous != null && previous.matches(chunkX, chunkZ, groups, grid)) {
|
||||||
|
previousCorners = recent;
|
||||||
|
recentCorners = previous;
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
ChunkCorners reused = previous != null && previous.fits(groups, grid) ? previous : new ChunkCorners(groups, grid);
|
||||||
|
reused.reset(chunkX, chunkZ);
|
||||||
|
previousCorners = recent;
|
||||||
|
recentCorners = reused;
|
||||||
|
return reused;
|
||||||
|
}
|
||||||
|
|
||||||
private int slot(int cornerX, int cornerZ, int interpolatorIndex) {
|
private int slot(int cornerX, int cornerZ, int interpolatorIndex) {
|
||||||
long h = (cornerX * 0x9E3779B97F4A7C15L) ^ (cornerZ * 0xC2B2AE3D27D4EB4FL) ^ (interpolatorIndex * 0x165667B19E3779F9L);
|
long h = (cornerX * 0x9E3779B97F4A7C15L) ^ (cornerZ * 0xC2B2AE3D27D4EB4FL) ^ (interpolatorIndex * 0x165667B19E3779F9L);
|
||||||
@@ -2254,6 +2380,92 @@ public class IrisComplex implements DataProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ChunkCorners {
|
||||||
|
private static final int UNKNOWN = 0;
|
||||||
|
private static final int SILENT = 1;
|
||||||
|
private static final int ACTIVE = 2;
|
||||||
|
|
||||||
|
private final int groups;
|
||||||
|
private final int grid;
|
||||||
|
private final int cornersPerAxis;
|
||||||
|
private final int cellsPerAxis;
|
||||||
|
private final long[] packed;
|
||||||
|
private final boolean[] known;
|
||||||
|
private final byte[] cellStates;
|
||||||
|
private int chunkX;
|
||||||
|
private int chunkZ;
|
||||||
|
private int originX;
|
||||||
|
private int originZ;
|
||||||
|
|
||||||
|
private ChunkCorners(int groups, int grid) {
|
||||||
|
this.groups = groups;
|
||||||
|
this.grid = grid;
|
||||||
|
cellsPerAxis = 16 / grid;
|
||||||
|
cornersPerAxis = cellsPerAxis + 1;
|
||||||
|
packed = new long[groups * cornersPerAxis * cornersPerAxis];
|
||||||
|
known = new boolean[packed.length];
|
||||||
|
cellStates = new byte[groups * cellsPerAxis * cellsPerAxis];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean supports(int grid) {
|
||||||
|
return grid >= 2 && grid <= 16 && Integer.bitCount(grid) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean fits(int groups, int grid) {
|
||||||
|
return this.groups == groups && this.grid == grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(int chunkX, int chunkZ, int groups, int grid) {
|
||||||
|
return this.chunkX == chunkX && this.chunkZ == chunkZ && fits(groups, grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset(int chunkX, int chunkZ) {
|
||||||
|
this.chunkX = chunkX;
|
||||||
|
this.chunkZ = chunkZ;
|
||||||
|
originX = chunkX << 4;
|
||||||
|
originZ = chunkZ << 4;
|
||||||
|
Arrays.fill(known, false);
|
||||||
|
Arrays.fill(cellStates, (byte) UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int cellState(int group, int cell) {
|
||||||
|
return cellStates[group * cellsPerAxis * cellsPerAxis + cell];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCellState(int group, int cell, int state) {
|
||||||
|
cellStates[group * cellsPerAxis * cellsPerAxis + cell] = (byte) state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean known(int group, int column, int row) {
|
||||||
|
return known[index(group, column, row)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private long corner(int group, int column, int row) {
|
||||||
|
return packed[index(group, column, row)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remember(int group, int column, int row, long value) {
|
||||||
|
int index = index(group, column, row);
|
||||||
|
packed[index] = value;
|
||||||
|
known[index] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void classify(int group, int column, int row, int cell) {
|
||||||
|
int first = index(group, column, row);
|
||||||
|
int second = first + cornersPerAxis;
|
||||||
|
if (!known[first] || !known[first + 1] || !known[second] || !known[second + 1]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean silent = silentBounds(packed[first]) && silentBounds(packed[first + 1])
|
||||||
|
&& silentBounds(packed[second]) && silentBounds(packed[second + 1]);
|
||||||
|
setCellState(group, cell, silent ? SILENT : ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int index(int group, int column, int row) {
|
||||||
|
return (group * cornersPerAxis + row) * cornersPerAxis + column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable per-thread bounds provider. Holds the biome memo and the lazily computed generator
|
* Reusable per-thread bounds provider. Holds the biome memo and the lazily computed generator
|
||||||
* bounds for one sampleBoundsRaw pass so the pass allocates nothing; {@link #bind}
|
* bounds for one sampleBoundsRaw pass so the pass allocates nothing; {@link #bind}
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
private final ChunkedDataCache<IrisBiome> biomeCache;
|
private final ChunkedDataCache<IrisBiome> biomeCache;
|
||||||
private final ChunkedDataCache<IrisRegion> regionCache;
|
private final ChunkedDataCache<IrisRegion> regionCache;
|
||||||
private final ChunkedDataCache<NativeBlockState> rockCache;
|
private final ChunkedDataCache<NativeBlockState> rockCache;
|
||||||
|
private final ChunkedDataCache<NativeBlockState> fluidCache;
|
||||||
private final UpperDimensionContext upperContext;
|
private final UpperDimensionContext upperContext;
|
||||||
private final IrisOreBands dimensionSurfaceOres;
|
private final IrisOreBands dimensionSurfaceOres;
|
||||||
private final IrisOreBands dimensionUndergroundOres;
|
private final IrisOreBands dimensionUndergroundOres;
|
||||||
@@ -192,6 +193,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
biomeCache = context.getBiome();
|
biomeCache = context.getBiome();
|
||||||
regionCache = context.getRegion();
|
regionCache = context.getRegion();
|
||||||
rockCache = context.getRock();
|
rockCache = context.getRock();
|
||||||
|
fluidCache = context.getFluid();
|
||||||
upperContext = getEngine().getUpperContext();
|
upperContext = getEngine().getUpperContext();
|
||||||
dimensionSurfaceOres = hideOres ? IrisOreBands.EMPTY : dimension.getSurfaceOreBands();
|
dimensionSurfaceOres = hideOres ? IrisOreBands.EMPTY : dimension.getSurfaceOreBands();
|
||||||
dimensionUndergroundOres = hideOres ? IrisOreBands.EMPTY : dimension.getUndergroundOreBands();
|
dimensionUndergroundOres = hideOres ? IrisOreBands.EMPTY : dimension.getUndergroundOreBands();
|
||||||
@@ -247,8 +249,10 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
IrisRiverMaterialConfig roleMaterial = hydrologyRoleMaterial(
|
IrisRiverMaterialConfig roleMaterial = hydrologyRoleMaterial(
|
||||||
hydrologyTerrain, bedMaterial, shoreMaterial, bankMaterial);
|
hydrologyTerrain, bedMaterial, shoreMaterial, bankMaterial);
|
||||||
int paintDepth = roleMaterial == null ? 0 : roleMaterial.getDepth();
|
int paintDepth = roleMaterial == null ? 0 : roleMaterial.getDepth();
|
||||||
NativeBlockState fluid = hydrologyFluid == null || transitionDisplaced
|
NativeBlockState fluid = transitionDisplaced
|
||||||
? complex.resolveSurfaceFluid(realX, realZ)
|
? complex.resolveSurfaceFluid(realX, realZ)
|
||||||
|
: hydrologyFluid == null
|
||||||
|
? fluidCache.get(xf, zf)
|
||||||
: complex.resolveHydrologyFluid(hydrologyFluid.profileKey(), realX, realZ);
|
: complex.resolveHydrologyFluid(hydrologyFluid.profileKey(), realX, realZ);
|
||||||
NativeBlockState rock = rockCache.get(xf, zf);
|
NativeBlockState rock = rockCache.get(xf, zf);
|
||||||
NativeBlockState mappedSurfaceBlock = complex.getImageMapRuntime().sampleSurfaceBlock(realX, realZ);
|
NativeBlockState mappedSurfaceBlock = complex.getImageMapRuntime().sampleSurfaceBlock(realX, realZ);
|
||||||
|
|||||||
+54
-4
@@ -95,9 +95,42 @@ public class ChunkContextPrefillPlanTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void singleOrNoFillTaskPrefillsInline() {
|
public void generationWorkersPrefillInlineWithoutConsultingSharedExecutor() {
|
||||||
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, 0));
|
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
|
||||||
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, 1));
|
platforms.when(IrisPlatforms::isBound).thenReturn(true);
|
||||||
|
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, false));
|
||||||
|
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.ALL, false));
|
||||||
|
platforms.verifyNoInteractions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void platformMainThreadKeepsBlockingPrefillOffTheTickThread() {
|
||||||
|
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
|
||||||
|
platforms.when(IrisPlatforms::isBound).thenReturn(true);
|
||||||
|
assertTrue(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NO_CAVE, true));
|
||||||
|
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NONE, true));
|
||||||
|
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NATURAL_TERRAIN, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void inlinePrefillFinishesHeightsBeforeTheBiomePassStarts() {
|
||||||
|
AtomicInteger caveCalls = new AtomicInteger();
|
||||||
|
AtomicInteger heightCalls = new AtomicInteger();
|
||||||
|
AtomicInteger biomeCalls = new AtomicInteger();
|
||||||
|
AtomicInteger rockCalls = new AtomicInteger();
|
||||||
|
AtomicInteger fluidCalls = new AtomicInteger();
|
||||||
|
AtomicInteger regionCalls = new AtomicInteger();
|
||||||
|
AtomicInteger heightsSeenByFirstBiome = new AtomicInteger(-1);
|
||||||
|
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
|
||||||
|
platforms.when(IrisPlatforms::isBound).thenReturn(true);
|
||||||
|
createContext(ChunkContext.PrefillPlan.NO_CAVE, caveCalls, heightCalls, biomeCalls, rockCalls,
|
||||||
|
fluidCalls, regionCalls, null, () -> heightsSeenByFirstBiome.compareAndSet(-1, heightCalls.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(256, heightsSeenByFirstBiome.get());
|
||||||
|
assertEquals(256, biomeCalls.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -110,7 +143,7 @@ public class ChunkContextPrefillPlanTest {
|
|||||||
AtomicInteger regionCalls = new AtomicInteger();
|
AtomicInteger regionCalls = new AtomicInteger();
|
||||||
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
|
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
|
||||||
platforms.when(IrisPlatforms::isBound).thenReturn(true);
|
platforms.when(IrisPlatforms::isBound).thenReturn(true);
|
||||||
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NATURAL_TERRAIN, 5));
|
assertFalse(ChunkContext.shouldPrefillAsync(ChunkContext.PrefillPlan.NATURAL_TERRAIN, true));
|
||||||
ChunkContext context = createContext(ChunkContext.PrefillPlan.NATURAL_TERRAIN,
|
ChunkContext context = createContext(ChunkContext.PrefillPlan.NATURAL_TERRAIN,
|
||||||
caveCalls, heightCalls, biomeCalls, rockCalls, fluidCalls, regionCalls);
|
caveCalls, heightCalls, biomeCalls, rockCalls, fluidCalls, regionCalls);
|
||||||
|
|
||||||
@@ -228,6 +261,22 @@ public class ChunkContextPrefillPlanTest {
|
|||||||
AtomicInteger fluidCalls,
|
AtomicInteger fluidCalls,
|
||||||
AtomicInteger regionCalls,
|
AtomicInteger regionCalls,
|
||||||
IrisHydrologyRuntime runtime
|
IrisHydrologyRuntime runtime
|
||||||
|
) {
|
||||||
|
return createContext(prefillPlan, caveCalls, heightCalls, biomeCalls, rockCalls, fluidCalls, regionCalls,
|
||||||
|
runtime, () -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkContext createContext(
|
||||||
|
ChunkContext.PrefillPlan prefillPlan,
|
||||||
|
AtomicInteger caveCalls,
|
||||||
|
AtomicInteger heightCalls,
|
||||||
|
AtomicInteger biomeCalls,
|
||||||
|
AtomicInteger rockCalls,
|
||||||
|
AtomicInteger fluidCalls,
|
||||||
|
AtomicInteger regionCalls,
|
||||||
|
IrisHydrologyRuntime runtime,
|
||||||
|
Runnable biomeObserver
|
||||||
) {
|
) {
|
||||||
IrisComplex complex = mock(IrisComplex.class);
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
|
||||||
@@ -244,6 +293,7 @@ public class ChunkContextPrefillPlanTest {
|
|||||||
ProceduralStream<IrisBiome> biomeStream = mock(ProceduralStream.class);
|
ProceduralStream<IrisBiome> biomeStream = mock(ProceduralStream.class);
|
||||||
IrisBiome biome = mock(IrisBiome.class);
|
IrisBiome biome = mock(IrisBiome.class);
|
||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
|
biomeObserver.run();
|
||||||
biomeCalls.incrementAndGet();
|
biomeCalls.incrementAndGet();
|
||||||
return biome;
|
return biome;
|
||||||
}).when(biomeStream).get(anyDouble(), anyDouble());
|
}).when(biomeStream).get(anyDouble(), anyDouble());
|
||||||
|
|||||||
+1
@@ -351,6 +351,7 @@ public class DimensionTerrain3DCompositionTest {
|
|||||||
when(context.getBiome()).thenReturn(biomes);
|
when(context.getBiome()).thenReturn(biomes);
|
||||||
when(context.getRegion()).thenReturn(regions);
|
when(context.getRegion()).thenReturn(regions);
|
||||||
when(context.getRock()).thenReturn(rocks);
|
when(context.getRock()).thenReturn(rocks);
|
||||||
|
when(context.getFluid()).thenReturn(mock(ChunkedDataCache.class));
|
||||||
when(context.getRoundedHeight(0, 0)).thenReturn(rootHeight);
|
when(context.getRoundedHeight(0, 0)).thenReturn(rootHeight);
|
||||||
Hunk<NativeBlockState> blocks = Hunk.newArrayHunk(1, 64, 1);
|
Hunk<NativeBlockState> blocks = Hunk.newArrayHunk(1, 64, 1);
|
||||||
new IrisTerrainNormalActuator(engine).paint(0, 0, blocks, context);
|
new IrisTerrainNormalActuator(engine).paint(0, 0, blocks, context);
|
||||||
|
|||||||
+289
@@ -0,0 +1,289 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.noise.IrisGenerator;
|
||||||
|
import art.arcane.iris.generation.noise.IrisInterpolator;
|
||||||
|
import art.arcane.volmlib.util.interpolation.NoiseBounds;
|
||||||
|
import art.arcane.volmlib.util.interpolation.NoiseBoundsProvider;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.SplittableRandom;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class IrisComplexChunkCornersTest {
|
||||||
|
@Test
|
||||||
|
public void chunkCornerHeightsMatchPerGroupGridSamplingBitForBit() throws Exception {
|
||||||
|
for (int step : new int[]{2, 4, 8, 16}) {
|
||||||
|
IrisInterpolator[] interpolators = {
|
||||||
|
new PatchInterpolator(0, 1D),
|
||||||
|
new PatchInterpolator(1, -3D),
|
||||||
|
new ZeroInterpolator(true),
|
||||||
|
new PatchInterpolator(2, 0.5D),
|
||||||
|
new ZeroInterpolator(false)
|
||||||
|
};
|
||||||
|
IrisGenerator[][] generators = {
|
||||||
|
{new WaveGenerator(1D), new WaveGenerator(0.5D)},
|
||||||
|
{new WaveGenerator(-0.25D)},
|
||||||
|
{new WaveGenerator(2D)},
|
||||||
|
{new WaveGenerator(0.75D), new WaveGenerator(-1D), new WaveGenerator(0.125D)},
|
||||||
|
{new WaveGenerator(4D)}
|
||||||
|
};
|
||||||
|
IrisComplex candidate = createComplex(step, interpolators, generators);
|
||||||
|
IrisComplex reference = createComplex(step, interpolators, generators);
|
||||||
|
SplittableRandom random = new SplittableRandom(step * 31L);
|
||||||
|
for (int sample = 0; sample < 60_000; sample++) {
|
||||||
|
double x = coordinate(random);
|
||||||
|
double z = coordinate(random);
|
||||||
|
assertEquals("step " + step + " at " + x + "," + z,
|
||||||
|
Double.doubleToRawLongBits(expectedHeight(reference, interpolators, generators, x, z)),
|
||||||
|
Double.doubleToRawLongBits(interpolatedHeight(candidate, x, z)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void silentCellsNeverEvaluateTheirGenerators() throws Exception {
|
||||||
|
WaveGenerator quiet = new WaveGenerator(1D);
|
||||||
|
WaveGenerator loud = new WaveGenerator(1D);
|
||||||
|
IrisInterpolator[] interpolators = {new ZeroInterpolator(true), new PatchInterpolator(0, 2D)};
|
||||||
|
IrisGenerator[][] generators = {{quiet}, {loud}};
|
||||||
|
IrisComplex complex = createComplex(4, interpolators, generators);
|
||||||
|
|
||||||
|
for (int x = 0; x < 64; x++) {
|
||||||
|
for (int z = 0; z < 64; z++) {
|
||||||
|
interpolatedHeight(complex, x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, quiet.invocations);
|
||||||
|
assertTrue(loud.invocations > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sparseSamplesFetchOnlyTheCornersTheirColumnsUse() throws Exception {
|
||||||
|
CountingInterpolator counting = new CountingInterpolator();
|
||||||
|
IrisInterpolator[] interpolators = {counting};
|
||||||
|
IrisGenerator[][] generators = {{new WaveGenerator(1D)}};
|
||||||
|
IrisComplex complex = createComplex(4, interpolators, generators);
|
||||||
|
|
||||||
|
for (int sample = 0; sample < 32; sample++) {
|
||||||
|
interpolatedHeight(complex, sample * 64D, sample * -64D);
|
||||||
|
}
|
||||||
|
assertEquals(32, counting.calls);
|
||||||
|
|
||||||
|
interpolatedHeight(complex, 2048D + 1D, 0D);
|
||||||
|
assertEquals(34, counting.calls);
|
||||||
|
interpolatedHeight(complex, 4096D, 1D);
|
||||||
|
assertEquals(36, counting.calls);
|
||||||
|
interpolatedHeight(complex, 8192D + 1D, 1D);
|
||||||
|
assertEquals(40, counting.calls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nestedHeightSamplesLeaveTheOuterChunkBlockIntact() throws Exception {
|
||||||
|
ReentrantGenerator reentrant = new ReentrantGenerator();
|
||||||
|
IrisInterpolator[] interpolators = {new ZeroFreeInterpolator(), new PatchInterpolator(4, -2D)};
|
||||||
|
IrisGenerator[][] generators = {{reentrant}, {new WaveGenerator(0.5D)}};
|
||||||
|
IrisComplex candidate = createComplex(4, interpolators, generators);
|
||||||
|
IrisComplex reference = createComplex(4, interpolators, generators);
|
||||||
|
SplittableRandom random = new SplittableRandom(99L);
|
||||||
|
for (int sample = 0; sample < 4_000; sample++) {
|
||||||
|
double x = coordinate(random);
|
||||||
|
double z = coordinate(random);
|
||||||
|
reentrant.target = reference;
|
||||||
|
double expected = expectedHeight(reference, interpolators, generators, x, z);
|
||||||
|
reentrant.target = candidate;
|
||||||
|
assertEquals(Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(interpolatedHeight(candidate, x, z)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonFiniteCornersFailTheSameWayAndAreRetried() throws Exception {
|
||||||
|
NaNCornerInterpolator interpolator = new NaNCornerInterpolator();
|
||||||
|
IrisInterpolator[] interpolators = {interpolator};
|
||||||
|
IrisGenerator[][] generators = {{new WaveGenerator(1D)}};
|
||||||
|
IrisComplex complex = createComplex(4, interpolators, generators);
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt < 2; attempt++) {
|
||||||
|
try {
|
||||||
|
interpolatedHeight(complex, 2D, 2D);
|
||||||
|
fail("A non-finite corner must fail the column");
|
||||||
|
} catch (InvocationTargetException expected) {
|
||||||
|
assertTrue(expected.getCause() instanceof IllegalStateException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(2, interpolator.nanCalls);
|
||||||
|
IrisComplex reference = createComplex(4, interpolators, generators);
|
||||||
|
assertEquals(Double.doubleToRawLongBits(expectedHeight(reference, interpolators, generators, 0D, 0D)),
|
||||||
|
Double.doubleToRawLongBits(interpolatedHeight(complex, 0D, 0D)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double coordinate(SplittableRandom random) {
|
||||||
|
int base = random.nextInt(-80, 80);
|
||||||
|
return switch (random.nextInt(4)) {
|
||||||
|
case 0 -> base;
|
||||||
|
case 1 -> base + random.nextInt(4) * 0.25D;
|
||||||
|
case 2 -> base + random.nextDouble();
|
||||||
|
default -> base * 1031 + random.nextInt(-3, 3);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double expectedHeight(
|
||||||
|
IrisComplex complex,
|
||||||
|
IrisInterpolator[] interpolators,
|
||||||
|
IrisGenerator[][] generators,
|
||||||
|
double x,
|
||||||
|
double z
|
||||||
|
) throws Exception {
|
||||||
|
Method interpolate = IrisComplex.class.getDeclaredMethod("interpolateGenerators", Engine.class,
|
||||||
|
IrisInterpolator.class, int.class, IrisGenerator[].class, double.class, double.class, long.class);
|
||||||
|
interpolate.setAccessible(true);
|
||||||
|
double height = 0;
|
||||||
|
for (int group = 0; group < interpolators.length; group++) {
|
||||||
|
height += (double) interpolate.invoke(complex, null, interpolators[group], group, generators[group], x, z, 31L);
|
||||||
|
}
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double interpolatedHeight(IrisComplex complex, double x, double z) throws Exception {
|
||||||
|
Method method = IrisComplex.class.getDeclaredMethod("getInterpolatedHeight", Engine.class,
|
||||||
|
double.class, double.class, long.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
return (double) method.invoke(complex, null, x, z, 31L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisComplex createComplex(int step, IrisInterpolator[] interpolators, IrisGenerator[][] generators) throws Exception {
|
||||||
|
IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS);
|
||||||
|
setField(complex, "biomeBoundsSamplingStep", step);
|
||||||
|
setField(complex, "generatorBounds", new HashMap<>());
|
||||||
|
setField(complex, "biomeBuffetGenerators", new HashMap<>());
|
||||||
|
setField(complex, "frozenInterpolators", interpolators);
|
||||||
|
setField(complex, "frozenGenerators", generators);
|
||||||
|
Class<?> cacheClass = Class.forName("art.arcane.iris.generation.runtime.IrisComplex$GridBoundsCache");
|
||||||
|
Constructor<?> cacheConstructor = cacheClass.getDeclaredConstructor();
|
||||||
|
cacheConstructor.setAccessible(true);
|
||||||
|
setField(complex, "gridBoundsCache", ThreadLocal.withInitial(() -> newCache(cacheConstructor)));
|
||||||
|
return complex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object newCache(Constructor<?> constructor) {
|
||||||
|
try {
|
||||||
|
return constructor.newInstance();
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setField(IrisComplex complex, String name, Object value) throws Exception {
|
||||||
|
Field field = IrisComplex.class.getDeclaredField(name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(complex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PatchInterpolator extends IrisInterpolator {
|
||||||
|
private final int salt;
|
||||||
|
private final double scale;
|
||||||
|
|
||||||
|
private PatchInterpolator(int salt, double scale) {
|
||||||
|
this.salt = salt;
|
||||||
|
this.scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
|
||||||
|
long cell = Math.floorDiv((long) x, 24L) * 73_856_093L ^ Math.floorDiv((long) z, 24L) * 19_349_663L ^ salt * 83_492_791L;
|
||||||
|
if (Math.floorMod(cell, 3L) != 0L) {
|
||||||
|
return new NoiseBounds(0D, 0D);
|
||||||
|
}
|
||||||
|
double low = (x * 0.0625D - z * 0.03125D) * scale;
|
||||||
|
return new NoiseBounds(low, low + 3.75D * scale + Math.floorMod(cell, 5L));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ZeroFreeInterpolator extends IrisInterpolator {
|
||||||
|
@Override
|
||||||
|
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
|
||||||
|
return new NoiseBounds(x * 0.01D - 1D, z * 0.01D + 3D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ZeroInterpolator extends IrisInterpolator {
|
||||||
|
private final boolean negative;
|
||||||
|
|
||||||
|
private ZeroInterpolator(boolean negative) {
|
||||||
|
this.negative = negative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
|
||||||
|
return negative ? new NoiseBounds(-0D, 0D) : new NoiseBounds(0D, -0D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CountingInterpolator extends IrisInterpolator {
|
||||||
|
private int calls;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
|
||||||
|
calls++;
|
||||||
|
return new NoiseBounds(x * 0.001D, x * 0.001D + 2D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class NaNCornerInterpolator extends IrisInterpolator {
|
||||||
|
private int nanCalls;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
|
||||||
|
if (x == 4D && z == 4D) {
|
||||||
|
nanCalls++;
|
||||||
|
return new NoiseBounds(Double.NaN, 1D);
|
||||||
|
}
|
||||||
|
return new NoiseBounds(0.25D, 0.5D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ReentrantGenerator extends IrisGenerator {
|
||||||
|
private IrisComplex target;
|
||||||
|
private boolean nested;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getHeight(double x, double z, long seed) {
|
||||||
|
if (nested) {
|
||||||
|
return Math.cos(x * 0.13D) * 0.5D;
|
||||||
|
}
|
||||||
|
nested = true;
|
||||||
|
try {
|
||||||
|
return (interpolatedHeight(target, x + 4099D, z - 2053D) + interpolatedHeight(target, x - 8191D, z + 1031D)) * 0.001D;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
} finally {
|
||||||
|
nested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class WaveGenerator extends IrisGenerator {
|
||||||
|
private final double amplitude;
|
||||||
|
private int invocations;
|
||||||
|
|
||||||
|
private WaveGenerator(double amplitude) {
|
||||||
|
this.amplitude = amplitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getHeight(double x, double z, long seed) {
|
||||||
|
invocations++;
|
||||||
|
return Math.sin(x * 0.37D + seed) * Math.cos(z * 0.21D) * amplitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiomeGeneratorLink;
|
||||||
|
import art.arcane.iris.generation.noise.IrisGenerator;
|
||||||
|
import art.arcane.iris.generation.noise.IrisInterpolator;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.interpolation.InterpolationMethod;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
public class IrisComplexContributingGroupsTest {
|
||||||
|
@Test
|
||||||
|
public void groupsWithZeroBoundsInEveryBaseBiomeAreDropped() {
|
||||||
|
IrisComplex.GeneratorGroup flat = group(InterpolationMethod.BILINEAR_STARCAST_6, 7D, "default");
|
||||||
|
IrisComplex.GeneratorGroup hills = group(InterpolationMethod.BILINEAR_STARCAST_9, 12D, "hills", "cliffs");
|
||||||
|
IrisComplex.GeneratorGroup shoreOnly = group(InterpolationMethod.BILINEAR_STARCAST_3, 30D, "ocean");
|
||||||
|
IrisBiome plains = biome(link("default", 0, 0), link("hills", -4, 12));
|
||||||
|
IrisBiome desert = biome(link("default", 0, 0), link("cliffs", 0, 3));
|
||||||
|
|
||||||
|
IrisComplex.GeneratorGroup[] contributing = IrisComplex.contributingGroups(
|
||||||
|
new IrisComplex.GeneratorGroup[]{flat, hills, shoreOnly}, List.of(plains, desert));
|
||||||
|
|
||||||
|
assertEquals(1, contributing.length);
|
||||||
|
assertSame(hills, contributing[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aNegativeOnlyOrMaximumOnlyLinkKeepsItsGroup() {
|
||||||
|
IrisComplex.GeneratorGroup sinks = group(InterpolationMethod.BILINEAR, 16D, "sinks");
|
||||||
|
IrisComplex.GeneratorGroup peaks = group(InterpolationMethod.STARCAST_12, 42D, "peaks");
|
||||||
|
|
||||||
|
IrisComplex.GeneratorGroup[] contributing = IrisComplex.contributingGroups(
|
||||||
|
new IrisComplex.GeneratorGroup[]{sinks, peaks},
|
||||||
|
List.of(biome(link("sinks", -6, 0)), biome(link("peaks", 0, 9))));
|
||||||
|
|
||||||
|
assertEquals(2, contributing.length);
|
||||||
|
assertSame(sinks, contributing[0]);
|
||||||
|
assertSame(peaks, contributing[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void linksThatCancelAcrossGeneratorsSumToZeroBoundsAndAreDropped() {
|
||||||
|
IrisComplex.GeneratorGroup pair = group(InterpolationMethod.BILINEAR_STARCAST_9, 52D, "rise", "fall");
|
||||||
|
|
||||||
|
IrisComplex.GeneratorGroup[] contributing = IrisComplex.contributingGroups(
|
||||||
|
new IrisComplex.GeneratorGroup[]{pair},
|
||||||
|
List.of(biome(link("rise", -5, 5), link("fall", 5, -5))));
|
||||||
|
|
||||||
|
assertEquals(0, contributing.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noBaseBiomesDropsEveryGroup() {
|
||||||
|
IrisComplex.GeneratorGroup hills = group(InterpolationMethod.BILINEAR_STARCAST_9, 12D, "hills");
|
||||||
|
|
||||||
|
assertEquals(0, IrisComplex.contributingGroups(new IrisComplex.GeneratorGroup[]{hills}, List.of()).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisComplex.GeneratorGroup group(InterpolationMethod function, double scale, String... keys) {
|
||||||
|
IrisInterpolator interpolator = new IrisInterpolator().setFunction(function).setHorizontalScale(scale);
|
||||||
|
IrisGenerator[] generators = new IrisGenerator[keys.length];
|
||||||
|
for (int index = 0; index < keys.length; index++) {
|
||||||
|
IrisGenerator generator = new IrisGenerator();
|
||||||
|
generator.setLoadKey(keys[index]);
|
||||||
|
generator.setInterpolator(interpolator);
|
||||||
|
generators[index] = generator;
|
||||||
|
}
|
||||||
|
return new IrisComplex.GeneratorGroup(interpolator, generators);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiome biome(IrisBiomeGeneratorLink... links) {
|
||||||
|
IrisBiome biome = new IrisBiome();
|
||||||
|
biome.setGenerators(new KList<>(List.of(links)));
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiomeGeneratorLink link(String generator, int min, int max) {
|
||||||
|
return new IrisBiomeGeneratorLink().setGenerator(generator).setMin(min).setMax(max);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.noise.CNG;
|
||||||
|
import art.arcane.volmlib.util.noise.OffsetNoiseGenerator;
|
||||||
|
import art.arcane.volmlib.util.noise.SimplexNoise;
|
||||||
|
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.SplittableRandom;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.withSettings;
|
||||||
|
|
||||||
|
public class IrisComplexShoreBandTest {
|
||||||
|
private static final double[] SHORE_EXTREMES = {0D, 1.2D, 3.2D, 2.5D, 7.75D, -1.5D, 4D, 0.0001D, 63.9D};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shoreHeightGeneratorIsAnIdentityPostSimplexSignature() {
|
||||||
|
for (int sample = 0; sample < 8; sample++) {
|
||||||
|
CNG generator = region(sample).getShoreHeightGenerator();
|
||||||
|
assertTrue(generator.getGenerator() instanceof OffsetNoiseGenerator);
|
||||||
|
assertTrue(((OffsetNoiseGenerator) generator.getGenerator()).getBase() instanceof SimplexNoise);
|
||||||
|
assertEquals(1D, generator.getOpacity(), 0D);
|
||||||
|
assertTrue(generator.getChildren() == null || generator.getChildren().isEmpty());
|
||||||
|
assertEquals(1D, generator.getPower(), 0D);
|
||||||
|
assertEquals(0D, generator.getUp(), 0D);
|
||||||
|
assertEquals(0D, generator.getDown(), 0D);
|
||||||
|
assertEquals(1D, generator.getPatch(), 0D);
|
||||||
|
assertNull(generator.getCache());
|
||||||
|
assertNull(generator.getCustomGenerator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simplexAmplitudeBoundFitsInsideTheShoreNoiseRange() {
|
||||||
|
double kernelPeak = 0D;
|
||||||
|
for (int step = 0; step <= 2_000_000; step++) {
|
||||||
|
double radius = Math.sqrt(0.5D) * step / 2_000_000D;
|
||||||
|
double falloff = 0.5D - radius * radius;
|
||||||
|
kernelPeak = Math.max(kernelPeak, falloff * falloff * falloff * falloff * Math.sqrt(2D) * radius);
|
||||||
|
}
|
||||||
|
assertEquals(256D / 19683D, kernelPeak, 1e-9D);
|
||||||
|
double amplitude = 50D * 3D * 256D / 19683D;
|
||||||
|
assertTrue(0.5D + amplitude / 2D < IrisComplex.SHORE_NOISE_HIGHEST);
|
||||||
|
assertTrue(0.5D - amplitude / 2D > IrisComplex.SHORE_NOISE_LOWEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shoreHeightNoiseStaysInsideTheProvenRange() {
|
||||||
|
SplittableRandom random = new SplittableRandom(1337L);
|
||||||
|
double lowest = Double.POSITIVE_INFINITY;
|
||||||
|
double highest = Double.NEGATIVE_INFINITY;
|
||||||
|
for (int sample = 0; sample < 4; sample++) {
|
||||||
|
CNG generator = region(sample).getShoreHeightGenerator();
|
||||||
|
for (int point = 0; point < 250_000; point++) {
|
||||||
|
double scale = point % 2 == 0 ? 1D : 64D;
|
||||||
|
double noise = generator.noise(random.nextDouble(-1e6D, 1e6D) / scale, random.nextDouble(-1e6D, 1e6D) / scale);
|
||||||
|
lowest = Math.min(lowest, noise);
|
||||||
|
highest = Math.max(highest, noise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue("lowest " + lowest, lowest >= IrisComplex.SHORE_NOISE_LOWEST);
|
||||||
|
assertTrue("highest " + highest, highest <= IrisComplex.SHORE_NOISE_HIGHEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void lazyShoreBandMatchesTheEagerBandTop() {
|
||||||
|
SplittableRandom random = new SplittableRandom(42L);
|
||||||
|
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 2D);
|
||||||
|
for (int sample = 0; sample < 400_000; sample++) {
|
||||||
|
IrisRegion region = region(random.nextInt(64))
|
||||||
|
.setShoreHeightMin(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
|
||||||
|
.setShoreHeightMax(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
|
||||||
|
.setShoreHeightZoom(random.nextBoolean() ? 3.14D : 0.15D)
|
||||||
|
.setShoreMinimumWidth(random.nextInt(8) == 0 ? 2D : 0D);
|
||||||
|
double fluid = random.nextInt(4) == 0 ? 63D : random.nextInt(-64, 200);
|
||||||
|
double height = random.nextInt(3) == 0
|
||||||
|
? fluid + random.nextDouble(-3D, 9D)
|
||||||
|
: random.nextInt(-64, 320) + (random.nextBoolean() ? 0D : random.nextDouble());
|
||||||
|
double x = random.nextInt(-30_000_000, 30_000_000);
|
||||||
|
double z = random.nextInt(-30_000_000, 30_000_000);
|
||||||
|
boolean eager = height > IrisComplex.shoreBandTop(height, region, x, z, fluid, slopes);
|
||||||
|
assertEquals(eager, IrisComplex.aboveShoreBand(height, region, x, z, fluid, slopes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void lazySurfaceBiomeMatchesTheEagerSelection() {
|
||||||
|
SplittableRandom random = new SplittableRandom(7L);
|
||||||
|
IrisBiome land = mock(IrisBiome.class, withSettings().stubOnly());
|
||||||
|
IrisBiome sea = mock(IrisBiome.class, withSettings().stubOnly());
|
||||||
|
IrisBiome shore = mock(IrisBiome.class, withSettings().stubOnly());
|
||||||
|
IrisBiome[] bases = new IrisBiome[8];
|
||||||
|
for (int flags = 0; flags < bases.length; flags++) {
|
||||||
|
IrisBiome base = mock(IrisBiome.class, withSettings().stubOnly());
|
||||||
|
doReturn((flags & 1) != 0).when(base).isShore();
|
||||||
|
doReturn((flags & 2) != 0).when(base).isLand();
|
||||||
|
doReturn((flags & 4) != 0).when(base).isAquatic();
|
||||||
|
bases[flags] = base;
|
||||||
|
}
|
||||||
|
ProceduralStream<IrisBiome> lands = constant(land);
|
||||||
|
ProceduralStream<IrisBiome> seas = constant(sea);
|
||||||
|
ProceduralStream<IrisBiome> shores = constant(shore);
|
||||||
|
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 4D);
|
||||||
|
for (int sample = 0; sample < 200_000; sample++) {
|
||||||
|
IrisRegion region = region(random.nextInt(16))
|
||||||
|
.setShoreHeightMin(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
|
||||||
|
.setShoreHeightMax(SHORE_EXTREMES[random.nextInt(SHORE_EXTREMES.length)])
|
||||||
|
.setShoreMinimumWidth(random.nextInt(8) == 0 ? 3D : 0D);
|
||||||
|
double fluid = 63D;
|
||||||
|
double height = random.nextBoolean() ? fluid + random.nextInt(-3, 8) : random.nextInt(-64, 320);
|
||||||
|
double x = random.nextInt(-100_000, 100_000);
|
||||||
|
double z = random.nextInt(-100_000, 100_000);
|
||||||
|
IrisBiome base = bases[random.nextInt(bases.length)];
|
||||||
|
IrisBiome expected = eagerSurfaceBiome(height, base, region, x, z, fluid, lands, seas, shores, slopes);
|
||||||
|
assertSame(expected, IrisComplex.resolveSurfaceBiome(height, base, region, x, z, fluid, lands, seas, shores, slopes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void columnsOutsideTheShoreWindowNeverSampleTheShoreNoise() {
|
||||||
|
IrisRegion region = mock(IrisRegion.class);
|
||||||
|
doReturn(1.2D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(3.2D).when(region).getShoreHeightMax();
|
||||||
|
doReturn(0D).when(region).getShoreMinimumWidth();
|
||||||
|
doThrow(new AssertionError("The shore noise was sampled for a column outside the shore window"))
|
||||||
|
.when(region).getShoreHeight(anyDouble(), anyDouble());
|
||||||
|
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 0D);
|
||||||
|
|
||||||
|
assertTrue(IrisComplex.aboveShoreBand(68D, region, 3D, 4D, 63D, slopes));
|
||||||
|
assertTrue(IrisComplex.aboveShoreBand(250D, region, 3D, 4D, 63D, slopes));
|
||||||
|
assertFalse(IrisComplex.aboveShoreBand(63.2D, region, 3D, 4D, 63D, slopes));
|
||||||
|
assertFalse(IrisComplex.aboveShoreBand(-20D, region, 3D, 4D, 63D, slopes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void equalShoreExtremesNeverSampleTheShoreNoise() {
|
||||||
|
IrisRegion region = mock(IrisRegion.class);
|
||||||
|
doReturn(2D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(2D).when(region).getShoreHeightMax();
|
||||||
|
doReturn(0D).when(region).getShoreMinimumWidth();
|
||||||
|
doThrow(new AssertionError("Equal shore extremes need no shore noise"))
|
||||||
|
.when(region).getShoreHeight(anyDouble(), anyDouble());
|
||||||
|
ProceduralStream<Double> slopes = ProceduralStream.ofDouble((x, z) -> 0D);
|
||||||
|
|
||||||
|
assertFalse(IrisComplex.aboveShoreBand(65D, region, 3D, 4D, 63D, slopes));
|
||||||
|
assertTrue(IrisComplex.aboveShoreBand(65.0001D, region, 3D, 4D, 63D, slopes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisRegion region(int variant) {
|
||||||
|
IrisRegion region = new IrisRegion();
|
||||||
|
region.setName("Shore Region " + variant);
|
||||||
|
region.setLandBiomeZoom(1D + variant * 0.25D);
|
||||||
|
KList<String> lands = new KList<>();
|
||||||
|
for (int index = 0; index < variant % 5; index++) {
|
||||||
|
lands.add("land-" + index);
|
||||||
|
}
|
||||||
|
region.setLandBiomes(lands);
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiome eagerSurfaceBiome(
|
||||||
|
double height,
|
||||||
|
IrisBiome biome,
|
||||||
|
IrisRegion region,
|
||||||
|
double x,
|
||||||
|
double z,
|
||||||
|
double fluidHeight,
|
||||||
|
ProceduralStream<IrisBiome> landBiomes,
|
||||||
|
ProceduralStream<IrisBiome> seaBiomes,
|
||||||
|
ProceduralStream<IrisBiome> shoreBiomes,
|
||||||
|
ProceduralStream<Double> shoreSlopes
|
||||||
|
) {
|
||||||
|
double shoreTop = IrisComplex.shoreBandTop(height, region, x, z, fluidHeight, shoreSlopes);
|
||||||
|
if (height >= fluidHeight - 1 && height <= shoreTop && !biome.isShore()) {
|
||||||
|
return shoreBiomes.get(x, z);
|
||||||
|
}
|
||||||
|
if (height > shoreTop && !biome.isLand()) {
|
||||||
|
return landBiomes.get(x, z);
|
||||||
|
}
|
||||||
|
if (height < fluidHeight && !biome.isAquatic()) {
|
||||||
|
return seaBiomes.get(x, z);
|
||||||
|
}
|
||||||
|
if (height == fluidHeight && !biome.isShore()) {
|
||||||
|
return shoreBiomes.get(x, z);
|
||||||
|
}
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProceduralStream<IrisBiome> constant(IrisBiome biome) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ProceduralStream<IrisBiome> stream = mock(ProceduralStream.class, withSettings().stubOnly());
|
||||||
|
doReturn(biome).when(stream).get(anyDouble(), anyDouble());
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -119,6 +119,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
doReturn(false).when(base).isShore();
|
doReturn(false).when(base).isShore();
|
||||||
doReturn(false).when(base).isAquatic();
|
doReturn(false).when(base).isAquatic();
|
||||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||||
|
doReturn(1D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(5D).when(region).getShoreHeightMax();
|
||||||
|
|
||||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||||
68D,
|
68D,
|
||||||
@@ -143,6 +145,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
IrisRegion region = mock(IrisRegion.class);
|
IrisRegion region = mock(IrisRegion.class);
|
||||||
doReturn(false).when(base).isShore();
|
doReturn(false).when(base).isShore();
|
||||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||||
|
doReturn(1D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(5D).when(region).getShoreHeightMax();
|
||||||
|
|
||||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||||
63D,
|
63D,
|
||||||
@@ -168,6 +172,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
doReturn(false).when(aquatic).isShore();
|
doReturn(false).when(aquatic).isShore();
|
||||||
doReturn(false).when(aquatic).isLand();
|
doReturn(false).when(aquatic).isLand();
|
||||||
doReturn(1D).when(region).getShoreHeight(4D, 7D);
|
doReturn(1D).when(region).getShoreHeight(4D, 7D);
|
||||||
|
doReturn(0.5D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(1.5D).when(region).getShoreHeightMax();
|
||||||
|
|
||||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||||
66D,
|
66D,
|
||||||
@@ -193,6 +199,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
doReturn(false).when(base).isShore();
|
doReturn(false).when(base).isShore();
|
||||||
doReturn(false).when(base).isLand();
|
doReturn(false).when(base).isLand();
|
||||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||||
|
doReturn(1D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(5D).when(region).getShoreHeightMax();
|
||||||
doReturn(0D).when(region).getShoreMinimumWidth();
|
doReturn(0D).when(region).getShoreMinimumWidth();
|
||||||
|
|
||||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||||
@@ -218,6 +226,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
IrisRegion region = mock(IrisRegion.class);
|
IrisRegion region = mock(IrisRegion.class);
|
||||||
doReturn(false).when(base).isShore();
|
doReturn(false).when(base).isShore();
|
||||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||||
|
doReturn(1D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(5D).when(region).getShoreHeightMax();
|
||||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||||
|
|
||||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||||
@@ -244,6 +254,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
doReturn(false).when(base).isShore();
|
doReturn(false).when(base).isShore();
|
||||||
doReturn(false).when(base).isLand();
|
doReturn(false).when(base).isLand();
|
||||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||||
|
doReturn(1D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(5D).when(region).getShoreHeightMax();
|
||||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||||
|
|
||||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||||
@@ -272,6 +284,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
doReturn(false).when(base).isLand();
|
doReturn(false).when(base).isLand();
|
||||||
doReturn(false).when(base).isAquatic();
|
doReturn(false).when(base).isAquatic();
|
||||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||||
|
doReturn(1D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(5D).when(region).getShoreHeightMax();
|
||||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||||
|
|
||||||
assertSame(sea, IrisComplex.resolveSurfaceBiome(
|
assertSame(sea, IrisComplex.resolveSurfaceBiome(
|
||||||
@@ -304,6 +318,8 @@ public class IrisComplexSurfaceBiomeTest {
|
|||||||
public void shoreBandTopTradesHorizontalWidthForHeightOnSlopes() {
|
public void shoreBandTopTradesHorizontalWidthForHeightOnSlopes() {
|
||||||
IrisRegion region = mock(IrisRegion.class);
|
IrisRegion region = mock(IrisRegion.class);
|
||||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||||
|
doReturn(1D).when(region).getShoreHeightMin();
|
||||||
|
doReturn(5D).when(region).getShoreHeightMax();
|
||||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||||
|
|
||||||
assertEquals(66D, IrisComplex.shoreBandTop(67D, region, 12D, 18D, 63D, constantSlope(0D)), 0D);
|
assertEquals(66D, IrisComplex.shoreBandTop(67D, region, 12D, 18D, 63D, constantSlope(0D)), 0D);
|
||||||
|
|||||||
+2
-1
@@ -175,7 +175,6 @@ public class IrisTerrainNormalActuatorColumnParityTest {
|
|||||||
when(complex.terrainColumn(eq(X), eq(Z), hydrology == null ? isNull() : any(HydrologyColumnSample.class)))
|
when(complex.terrainColumn(eq(X), eq(Z), hydrology == null ? isNull() : any(HydrologyColumnSample.class)))
|
||||||
.thenReturn(terrainColumn);
|
.thenReturn(terrainColumn);
|
||||||
when(complex.naturalTerrainColumn(X, Z)).thenReturn(terrainColumn);
|
when(complex.naturalTerrainColumn(X, Z)).thenReturn(terrainColumn);
|
||||||
when(complex.resolveSurfaceFluid(anyDouble(), anyDouble())).thenReturn(fluid);
|
|
||||||
when(context.getRoundedHeight(0, 0)).thenReturn(he);
|
when(context.getRoundedHeight(0, 0)).thenReturn(he);
|
||||||
when(context.getBiome()).thenReturn(new ChunkedDataCache<>(
|
when(context.getBiome()).thenReturn(new ChunkedDataCache<>(
|
||||||
ProceduralStream.of((x, z) -> biome, Interpolated.of(value -> 0D, value -> biome)), X, Z, false));
|
ProceduralStream.of((x, z) -> biome, Interpolated.of(value -> 0D, value -> biome)), X, Z, false));
|
||||||
@@ -183,6 +182,8 @@ public class IrisTerrainNormalActuatorColumnParityTest {
|
|||||||
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), X, Z, false));
|
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), X, Z, false));
|
||||||
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
|
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
|
||||||
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), X, Z, false));
|
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), X, Z, false));
|
||||||
|
when(context.getFluid()).thenReturn(new ChunkedDataCache<>(
|
||||||
|
ProceduralStream.of((x, z) -> fluid, Interpolated.of(value -> 0D, value -> fluid)), X, Z, false));
|
||||||
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
||||||
anyInt(), anyInt(), eq(data), eq(complex))).thenAnswer(invocation -> layers("layer", layerCount));
|
anyInt(), anyInt(), eq(data), eq(complex))).thenAnswer(invocation -> layers("layer", layerCount));
|
||||||
when(biome.generateSeaLayers(anyDouble(), anyDouble(), any(RNG.class), anyInt(), eq(data)))
|
when(biome.generateSeaLayers(anyDouble(), anyDouble(), any(RNG.class), anyInt(), eq(data)))
|
||||||
|
|||||||
+2
@@ -259,6 +259,8 @@ public class IrisTerrainNormalActuatorHydrologyTest {
|
|||||||
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), 11, -4, false));
|
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), 11, -4, false));
|
||||||
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
|
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
|
||||||
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), 11, -4, false));
|
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), 11, -4, false));
|
||||||
|
when(context.getFluid()).thenReturn(new ChunkedDataCache<>(
|
||||||
|
ProceduralStream.of((x, z) -> null, Interpolated.of(value -> 0D, value -> null)), 11, -4, false));
|
||||||
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
||||||
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>(List.of(grass)));
|
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>(List.of(grass)));
|
||||||
Hunk<NativeBlockState> output = Hunk.newArrayHunk(1, 64, 1);
|
Hunk<NativeBlockState> output = Hunk.newArrayHunk(1, 64, 1);
|
||||||
|
|||||||
+2
@@ -155,6 +155,8 @@ public class IrisTerrainNormalActuatorStrataCutTest {
|
|||||||
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), 11, -4, false));
|
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), 11, -4, false));
|
||||||
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
|
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
|
||||||
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), 11, -4, false));
|
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), 11, -4, false));
|
||||||
|
when(context.getFluid()).thenReturn(new ChunkedDataCache<>(
|
||||||
|
ProceduralStream.of((x, z) -> null, Interpolated.of(value -> 0D, value -> null)), 11, -4, false));
|
||||||
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
||||||
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>(List.of(grass, dirt)));
|
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>(List.of(grass, dirt)));
|
||||||
when(biome.generateCeilingLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
when(biome.generateCeilingLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
||||||
|
|||||||
Reference in new issue
Block a user