mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Trimmed height pipeline work
- Removed the unused decoration, derivative and identity streams and Engine.getBiomeID - Skipped the shore-height noise when a column sits outside the proven shore band - Dropped interpolator groups whose bounds are zero in every base-capable biome
This commit is contained in:
1 parent
e2430500cf
commit
c1732d50be
5 files changed
+396
-71
No files matched your search
@@ -77,7 +77,6 @@ import java.awt.Color;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -224,10 +223,6 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
||||
|
||||
EngineWorldManager getWorldManager();
|
||||
|
||||
default UUID getBiomeID(int x, int z) {
|
||||
return getComplex().getBaseBiomeIDStream().get(x, z);
|
||||
}
|
||||
|
||||
int getParallelism();
|
||||
|
||||
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.pack.loading.IrisData;
|
||||
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.world.history.GenerationBlend;
|
||||
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.IrisBiomeGeneratorLink;
|
||||
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.IrisGeneratorStyle;
|
||||
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.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.generation.block.DataProvider;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
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.NoiseBoundsProvider;
|
||||
import art.arcane.volmlib.util.noise.CNG;
|
||||
@@ -99,8 +96,10 @@ import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
@@ -133,6 +132,8 @@ public class IrisComplex implements DataProvider {
|
||||
private static final double SLOPE_RUN = 3D;
|
||||
/** 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;
|
||||
static final double SHORE_NOISE_LOWEST = -0.5D;
|
||||
static final double SHORE_NOISE_HIGHEST = 1.5D;
|
||||
private static final Comparator<IrisInterpolator> INTERPOLATOR_ORDER = Comparator
|
||||
.comparing((IrisInterpolator interpolator) -> interpolator.getFunction().name())
|
||||
.thenComparingDouble(IrisInterpolator::getHorizontalScale);
|
||||
@@ -176,7 +177,6 @@ public class IrisComplex implements DataProvider {
|
||||
private ProceduralStream<IrisRegion> regionStream;
|
||||
private ProceduralStream<Double> regionStyleStream;
|
||||
private ProceduralStream<Double> regionIdentityStream;
|
||||
private ProceduralStream<UUID> regionIDStream;
|
||||
private ProceduralStream<InferredType> bridgeStream;
|
||||
private ProceduralStream<IrisBiome> landBiomeStream;
|
||||
private ProceduralStream<IrisBiome> caveBiomeStream;
|
||||
@@ -185,10 +185,8 @@ public class IrisComplex implements DataProvider {
|
||||
private ProceduralStream<IrisBiome> baseBiomeStream;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient boolean reuseNaturalBaseBiome;
|
||||
private ProceduralStream<UUID> baseBiomeIDStream;
|
||||
private ProceduralStream<IrisBiome> naturalTrueBiomeStream;
|
||||
private ProceduralStream<IrisBiome> trueBiomeStream;
|
||||
private ProceduralStream<NativeBiome> trueBiomeDerivativeStream;
|
||||
private ProceduralStream<Double> naturalHeightStream;
|
||||
private ProceduralStream<Double> baseTerrainHeightStream;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@@ -230,13 +228,6 @@ public class IrisComplex implements DataProvider {
|
||||
private ProceduralStream<Double> riverCarveWeightStream;
|
||||
private ProceduralStream<Double> riverWaterSurfaceStream;
|
||||
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> fluidStream;
|
||||
private Map<String, ProceduralStream<NativeBlockState>> hydrologyFluidStreams;
|
||||
@@ -350,7 +341,9 @@ public class IrisComplex implements DataProvider {
|
||||
preparedRegions,
|
||||
(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;
|
||||
frozenInterpolators = new IrisInterpolator[interpolatorCount];
|
||||
frozenGenerators = new IrisGenerator[interpolatorCount][];
|
||||
@@ -384,8 +377,6 @@ public class IrisComplex implements DataProvider {
|
||||
: GenerationStreams.cache2D(RegionSelection.select(new RegionSelection.Options(
|
||||
engine.getDimension(), data, regionStyleStream, imageMapRuntime, null)),
|
||||
"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))
|
||||
.convert((r) -> createInferredBiomeStream(r, InferredType.CAVE))
|
||||
.convertAware2D(ProceduralStream::get), "caveBiomeStream", engine, cacheSize);
|
||||
@@ -487,8 +478,6 @@ public class IrisComplex implements DataProvider {
|
||||
shoreSlopeStream = terrain3DEnabled ? baseTerrainHeightStream.slope(3) : naturalSlopeStream;
|
||||
trueBiomeStream = focusedBiomes != null ? GenerationStreams.cache2D(focusedBiomes, "trueBiomeStream-focus", engine, cacheSize) : GenerationStreams.cache2D(heightStream
|
||||
.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);
|
||||
riverFlowStream = GenerationStreams.cache2DDouble(ProceduralStream.ofDouble(this::resolveHydrologyFlow), "riverFlowStream", 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)
|
||||
)), "heightFluidStream", engine, cacheSize);
|
||||
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
|
||||
TerrainTransformer transformer = engine.getDimension().getTerrainTransform() == null ? null
|
||||
: TerrainTransformRegistry.discover().resolve(engine.getDimension().getTerrainTransform());
|
||||
@@ -1629,24 +1598,6 @@ public class IrisComplex implements DataProvider {
|
||||
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) {
|
||||
HydrologyColumnSample sample = hydrologySample(x, z);
|
||||
HydrologyColumnLayer layer = sample == null ? null : sample.primarySurfaceLayer().orElse(null);
|
||||
@@ -1730,6 +1681,33 @@ public class IrisComplex implements DataProvider {
|
||||
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(
|
||||
double height,
|
||||
IrisBiome biome,
|
||||
@@ -1745,13 +1723,15 @@ public class IrisComplex implements DataProvider {
|
||||
if (biome == null || region == null) {
|
||||
return biome;
|
||||
}
|
||||
double shoreTop = 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()) {
|
||||
if (height >= fluidHeight - 1 && !biome.isShore()) {
|
||||
if (!aboveShoreBand(height, region, x, z, fluidHeight, shoreSlopes)) {
|
||||
return shoreBiomes.get(x, z);
|
||||
}
|
||||
if (!biome.isLand()) {
|
||||
return landBiomes.get(x, z);
|
||||
}
|
||||
} else if (!biome.isLand() && aboveShoreBand(height, region, x, z, fluidHeight, shoreSlopes)) {
|
||||
return landBiomes.get(x, z);
|
||||
}
|
||||
|
||||
@@ -2058,6 +2038,48 @@ public class IrisComplex implements DataProvider {
|
||||
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(
|
||||
IrisGenerator[] generators,
|
||||
double low,
|
||||
@@ -2103,7 +2125,7 @@ public class IrisComplex implements DataProvider {
|
||||
for (int i = 0; i < frozenInterpolators.length; i++) {
|
||||
IdentityHashMap<IrisBiome, GeneratorBounds> interpolatorBounds = new IdentityHashMap<>(Math.max(allBiomes.size(), 16));
|
||||
for (IrisBiome biome : allBiomes) {
|
||||
interpolatorBounds.put(biome, computeGeneratorBounds(engine, frozenGenerators[i], biome));
|
||||
interpolatorBounds.put(biome, computeGeneratorBounds(frozenGenerators[i], biome));
|
||||
}
|
||||
bounds.put(frozenInterpolators[i], interpolatorBounds);
|
||||
}
|
||||
@@ -2111,7 +2133,7 @@ public class IrisComplex implements DataProvider {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private GeneratorBounds computeGeneratorBounds(Engine engine, IrisGenerator[] generators, IrisBiome biome) {
|
||||
private static GeneratorBounds computeGeneratorBounds(IrisGenerator[] generators, IrisBiome biome) {
|
||||
double min = 0D;
|
||||
double max = 0D;
|
||||
|
||||
@@ -2145,7 +2167,7 @@ public class IrisComplex implements DataProvider {
|
||||
return local;
|
||||
}
|
||||
|
||||
GeneratorBounds computed = computeGeneratorBounds(engine, generators, biome);
|
||||
GeneratorBounds computed = computeGeneratorBounds(generators, biome);
|
||||
localBounds.put(biome, computed);
|
||||
return computed;
|
||||
}
|
||||
|
||||
+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).isAquatic();
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
doReturn(1D).when(region).getShoreHeightMin();
|
||||
doReturn(5D).when(region).getShoreHeightMax();
|
||||
|
||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||
68D,
|
||||
@@ -143,6 +145,8 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
IrisRegion region = mock(IrisRegion.class);
|
||||
doReturn(false).when(base).isShore();
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
doReturn(1D).when(region).getShoreHeightMin();
|
||||
doReturn(5D).when(region).getShoreHeightMax();
|
||||
|
||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||
63D,
|
||||
@@ -168,6 +172,8 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
doReturn(false).when(aquatic).isShore();
|
||||
doReturn(false).when(aquatic).isLand();
|
||||
doReturn(1D).when(region).getShoreHeight(4D, 7D);
|
||||
doReturn(0.5D).when(region).getShoreHeightMin();
|
||||
doReturn(1.5D).when(region).getShoreHeightMax();
|
||||
|
||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||
66D,
|
||||
@@ -193,6 +199,8 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
doReturn(false).when(base).isShore();
|
||||
doReturn(false).when(base).isLand();
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
doReturn(1D).when(region).getShoreHeightMin();
|
||||
doReturn(5D).when(region).getShoreHeightMax();
|
||||
doReturn(0D).when(region).getShoreMinimumWidth();
|
||||
|
||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||
@@ -218,6 +226,8 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
IrisRegion region = mock(IrisRegion.class);
|
||||
doReturn(false).when(base).isShore();
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
doReturn(1D).when(region).getShoreHeightMin();
|
||||
doReturn(5D).when(region).getShoreHeightMax();
|
||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||
|
||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||
@@ -244,6 +254,8 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
doReturn(false).when(base).isShore();
|
||||
doReturn(false).when(base).isLand();
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
doReturn(1D).when(region).getShoreHeightMin();
|
||||
doReturn(5D).when(region).getShoreHeightMax();
|
||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||
|
||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||
@@ -272,6 +284,8 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
doReturn(false).when(base).isLand();
|
||||
doReturn(false).when(base).isAquatic();
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
doReturn(1D).when(region).getShoreHeightMin();
|
||||
doReturn(5D).when(region).getShoreHeightMax();
|
||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||
|
||||
assertSame(sea, IrisComplex.resolveSurfaceBiome(
|
||||
@@ -304,6 +318,8 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
public void shoreBandTopTradesHorizontalWidthForHeightOnSlopes() {
|
||||
IrisRegion region = mock(IrisRegion.class);
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
doReturn(1D).when(region).getShoreHeightMin();
|
||||
doReturn(5D).when(region).getShoreHeightMax();
|
||||
doReturn(3D).when(region).getShoreMinimumWidth();
|
||||
|
||||
assertEquals(66D, IrisComplex.shoreBandTop(67D, region, 12D, 18D, 63D, constantSlope(0D)), 0D);
|
||||
|
||||
Reference in new issue
Block a user