mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Merge W2C-terrain
This commit is contained in:
@@ -24,7 +24,7 @@ import art.arcane.iris.generation.decoration.IrisDecorator;
|
|||||||
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
||||||
import art.arcane.iris.generation.decoration.IrisDepositVariant;
|
import art.arcane.iris.generation.decoration.IrisDepositVariant;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGeneratorBounds;
|
import art.arcane.iris.generation.decoration.IrisOreBands;
|
||||||
import art.arcane.iris.generation.decoration.IrisProceduralObjects;
|
import art.arcane.iris.generation.decoration.IrisProceduralObjects;
|
||||||
import art.arcane.iris.generation.hydrology.IrisRiverPolicy;
|
import art.arcane.iris.generation.hydrology.IrisRiverPolicy;
|
||||||
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||||
@@ -120,8 +120,8 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
|
|||||||
private final transient IrisBiomeLayerHeightCache layerSeaHeightGenerators = new IrisBiomeLayerHeightCache();
|
private final transient IrisBiomeLayerHeightCache layerSeaHeightGenerators = new IrisBiomeLayerHeightCache();
|
||||||
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
|
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreBands> surfaceOreBandsCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreBands> undergroundOreBandsCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<KSet<String>> surfaceOreReplaceableBlockData = new AtomicCache<>();
|
private final transient AtomicCache<KSet<String>> surfaceOreReplaceableBlockData = new AtomicCache<>();
|
||||||
private final transient AtomicCache<EnumMap<IrisDecorationPart, IrisDecorator[]>> decoratorBuckets = new AtomicCache<>();
|
private final transient AtomicCache<EnumMap<IrisDecorationPart, IrisDecorator[]>> decoratorBuckets = new AtomicCache<>();
|
||||||
private final transient AtomicCache<Biome> derivativeResolved = new AtomicCache<>();
|
private final transient AtomicCache<Biome> derivativeResolved = new AtomicCache<>();
|
||||||
@@ -338,8 +338,8 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
|
|||||||
this.ores = ores == null ? new KList<>() : ores;
|
this.ores = ores == null ? new KList<>() : ores;
|
||||||
surfaceOreCache.reset();
|
surfaceOreCache.reset();
|
||||||
undergroundOreCache.reset();
|
undergroundOreCache.reset();
|
||||||
surfaceOreBoundsCache.reset();
|
surfaceOreBandsCache.reset();
|
||||||
undergroundOreBoundsCache.reset();
|
undergroundOreBandsCache.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IrisBiome setSurfaceOreReplaceableBlocks(KList<String> surfaceOreReplaceableBlocks) {
|
public IrisBiome setSurfaceOreReplaceableBlocks(KList<String> surfaceOreReplaceableBlocks) {
|
||||||
@@ -348,20 +348,12 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KList<IrisOreGenerator> getSurfaceOreGenerators() {
|
public IrisOreBands getSurfaceOreBands() {
|
||||||
return IrisBiomeOres.getSurfaceOreGenerators(this);
|
return IrisBiomeOres.getSurfaceOreBands(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KList<IrisOreGenerator> getUndergroundOreGenerators() {
|
public IrisOreBands getUndergroundOreBands() {
|
||||||
return IrisBiomeOres.getUndergroundOreGenerators(this);
|
return IrisBiomeOres.getUndergroundOreBands(this);
|
||||||
}
|
|
||||||
|
|
||||||
public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() {
|
|
||||||
return IrisBiomeOres.getSurfaceOreGeneratorBounds(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() {
|
|
||||||
return IrisBiomeOres.getUndergroundOreGeneratorBounds(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Biome getDerivative() {
|
public Biome getDerivative() {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
package art.arcane.iris.generation.biome;
|
package art.arcane.iris.generation.biome;
|
||||||
|
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGeneratorBounds;
|
import art.arcane.iris.generation.decoration.IrisOreBands;
|
||||||
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
||||||
|
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
@@ -73,30 +73,22 @@ final class IrisBiomeOres {
|
|||||||
.contains(IrisProceduralBlocks.materialKey(state));
|
.contains(IrisProceduralBlocks.materialKey(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
static KList<IrisOreGenerator> getSurfaceOreGenerators(IrisBiome biome) {
|
static IrisOreBands getSurfaceOreBands(IrisBiome biome) {
|
||||||
return getOres(biome, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static KList<IrisOreGenerator> getUndergroundOreGenerators(IrisBiome biome) {
|
|
||||||
return getOres(biome, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static IrisOreGeneratorBounds getSurfaceOreGeneratorBounds(IrisBiome biome) {
|
|
||||||
// getIfPresent fast path: aquire allocates a capturing lambda even on a hit, and this
|
// getIfPresent fast path: aquire allocates a capturing lambda even on a hit, and this
|
||||||
// runs per column on the terrain hot path.
|
// runs per column on the terrain hot path.
|
||||||
IrisOreGeneratorBounds cached = biome.getSurfaceOreBoundsCache().getIfPresent();
|
IrisOreBands cached = biome.getSurfaceOreBandsCache().getIfPresent();
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
return biome.getSurfaceOreBoundsCache().aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres(biome)));
|
return biome.getSurfaceOreBandsCache().aquire(() -> IrisOreBands.of(getSurfaceOres(biome)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static IrisOreGeneratorBounds getUndergroundOreGeneratorBounds(IrisBiome biome) {
|
static IrisOreBands getUndergroundOreBands(IrisBiome biome) {
|
||||||
IrisOreGeneratorBounds cached = biome.getUndergroundOreBoundsCache().getIfPresent();
|
IrisOreBands cached = biome.getUndergroundOreBandsCache().getIfPresent();
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
return biome.getUndergroundOreBoundsCache().aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres(biome)));
|
return biome.getUndergroundOreBandsCache().aquire(() -> IrisOreBands.of(getUndergroundOres(biome)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KList<IrisOreGenerator> getSurfaceOres(IrisBiome biome) {
|
private static KList<IrisOreGenerator> getSurfaceOres(IrisBiome biome) {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import lombok.Setter;
|
|||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||||
|
|
||||||
@Snippet("biome-palette")
|
@Snippet("biome-palette")
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
@@ -64,7 +65,8 @@ public class IrisBiomePaletteLayer {
|
|||||||
new LazyBoundedCache<>(LAYER_GENERATOR_CACHE_SIZE);
|
new LazyBoundedCache<>(LAYER_GENERATOR_CACHE_SIZE);
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
private final transient AtomicReference<CachedLayerGenerator> recentLayerGenerator = new AtomicReference<>();
|
private final transient AtomicReferenceArray<CachedLayerGenerator> recentLayerGenerators =
|
||||||
|
new AtomicReferenceArray<>(LAYER_GENERATOR_CACHE_SIZE);
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
private final transient LazyBoundedCache<LayerGeneratorKey, CNG> heightGenerators =
|
private final transient LazyBoundedCache<LayerGeneratorKey, CNG> heightGenerators =
|
||||||
@@ -145,7 +147,8 @@ public class IrisBiomePaletteLayer {
|
|||||||
public CNG getLayerGenerator(RNG parent, int signature, IrisData data) {
|
public CNG getLayerGenerator(RNG parent, int signature, IrisData data) {
|
||||||
Engine engine = data == null ? null : data.getEngine();
|
Engine engine = data == null ? null : data.getEngine();
|
||||||
long generatorSeed = parent.getSeed() + signature + minHeight + maxHeight + getBlockData(data).size();
|
long generatorSeed = parent.getSeed() + signature + minHeight + maxHeight + getBlockData(data).size();
|
||||||
CachedLayerGenerator recent = recentLayerGenerator.get();
|
int slot = (int) generatorSeed & (LAYER_GENERATOR_CACHE_SIZE - 1);
|
||||||
|
CachedLayerGenerator recent = recentLayerGenerators.getAcquire(slot);
|
||||||
if (recent != null && recent.key.matches(data, engine, generatorSeed)) {
|
if (recent != null && recent.key.matches(data, engine, generatorSeed)) {
|
||||||
return recent.generator;
|
return recent.generator;
|
||||||
}
|
}
|
||||||
@@ -154,7 +157,7 @@ public class IrisBiomePaletteLayer {
|
|||||||
CNG generator = layerGenerators.computeIfAbsent(key,
|
CNG generator = layerGenerators.computeIfAbsent(key,
|
||||||
ignored -> style.create(new RNG(generatorSeed), data, engine));
|
ignored -> style.create(new RNG(generatorSeed), data, engine));
|
||||||
if (generator != null) {
|
if (generator != null) {
|
||||||
recentLayerGenerator.set(new CachedLayerGenerator(key, generator));
|
recentLayerGenerators.setRelease(slot, new CachedLayerGenerator(key, generator));
|
||||||
}
|
}
|
||||||
return generator;
|
return generator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,20 +18,50 @@
|
|||||||
|
|
||||||
package art.arcane.iris.generation.chunk;
|
package art.arcane.iris.generation.chunk;
|
||||||
|
|
||||||
import art.arcane.volmlib.util.function.Consumer4;
|
|
||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
import art.arcane.volmlib.util.hunk.view.ListeningHunk;
|
|
||||||
|
|
||||||
public final class ColumnExtentListeningHunk<T> extends ListeningHunk<T> implements ColumnExtent {
|
public final class ColumnExtentListeningHunk<T> implements Hunk<T>, ColumnExtent {
|
||||||
private final Hunk<T> source;
|
private final Hunk<T> source;
|
||||||
|
private final Listener<T> listener;
|
||||||
|
|
||||||
public ColumnExtentListeningHunk(Hunk<T> source, Consumer4<Integer, Integer, Integer, T> listener) {
|
public ColumnExtentListeningHunk(Hunk<T> source, Listener<T> listener) {
|
||||||
super(source, listener);
|
|
||||||
this.source = source;
|
this.source = source;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRaw(int x, int y, int z, T t) {
|
||||||
|
listener.onWrite(x, y, z, t);
|
||||||
|
source.setRaw(x, y, z, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getRaw(int x, int y, int z) {
|
||||||
|
return source.getRaw(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWidth() {
|
||||||
|
return source.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight() {
|
||||||
|
return source.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDepth() {
|
||||||
|
return source.getDepth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int highestStoredY(int x, int z) {
|
public int highestStoredY(int x, int z) {
|
||||||
return ColumnExtent.highestStoredY(source, x, z);
|
return ColumnExtent.highestStoredY(source, x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Listener<T> {
|
||||||
|
void onWrite(int x, int y, int z, T value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package art.arcane.iris.generation.decoration;
|
||||||
|
|
||||||
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.pack.value.IrisRange;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One ore list (a biome's, region's or dimension's surface or underground ores) indexed by Y. Each Y
|
||||||
|
* holds the generators whose range contains it, in list order, so resolving a block tries exactly
|
||||||
|
* the generators that could place there and still returns the first one that does.
|
||||||
|
*/
|
||||||
|
public final class IrisOreBands {
|
||||||
|
public static final IrisOreBands EMPTY = new IrisOreBands(new IrisOreGenerator[0], 0, -1, null);
|
||||||
|
private static final int MAXIMUM_INDEXED_HEIGHT = 8192;
|
||||||
|
|
||||||
|
private final IrisOreGenerator[] generators;
|
||||||
|
private final int minY;
|
||||||
|
private final int maxY;
|
||||||
|
private final IrisOreGenerator[][] byY;
|
||||||
|
|
||||||
|
private IrisOreBands(IrisOreGenerator[] generators, int minY, int maxY, IrisOreGenerator[][] byY) {
|
||||||
|
this.generators = generators;
|
||||||
|
this.minY = minY;
|
||||||
|
this.maxY = maxY;
|
||||||
|
this.byY = byY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisOreBands of(KList<IrisOreGenerator> ores) {
|
||||||
|
if (ores == null || ores.isEmpty()) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisOreGenerator[] generators = ores.toArray(new IrisOreGenerator[0]);
|
||||||
|
double min = Double.POSITIVE_INFINITY;
|
||||||
|
double max = Double.NEGATIVE_INFINITY;
|
||||||
|
for (IrisOreGenerator generator : generators) {
|
||||||
|
IrisRange range = generator.getRange();
|
||||||
|
min = Math.min(min, range.getMin());
|
||||||
|
max = Math.max(max, range.getMax());
|
||||||
|
}
|
||||||
|
if (!(min <= max)) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minY = (int) Math.ceil(min);
|
||||||
|
int maxY = (int) Math.floor(max);
|
||||||
|
if (minY > maxY) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
long height = (long) maxY - minY + 1L;
|
||||||
|
return new IrisOreBands(generators, minY, maxY,
|
||||||
|
height > MAXIMUM_INDEXED_HEIGHT ? null : index(generators, minY, (int) height));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisOreGenerator[][] index(IrisOreGenerator[] generators, int minY, int height) {
|
||||||
|
IrisOreGenerator[][] byY = new IrisOreGenerator[height][];
|
||||||
|
IrisOreGenerator[] scratch = new IrisOreGenerator[generators.length];
|
||||||
|
IrisOreGenerator[] previous = null;
|
||||||
|
for (int offset = 0; offset < height; offset++) {
|
||||||
|
int y = minY + offset;
|
||||||
|
int count = 0;
|
||||||
|
for (IrisOreGenerator generator : generators) {
|
||||||
|
if (generator.getRange().contains(y)) {
|
||||||
|
scratch[count++] = generator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IrisOreGenerator[] candidates = Arrays.copyOf(scratch, count);
|
||||||
|
byY[offset] = previous != null && Arrays.equals(previous, candidates) ? previous : candidates;
|
||||||
|
previous = byY[offset];
|
||||||
|
}
|
||||||
|
return byY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOres() {
|
||||||
|
return minY <= maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(int y) {
|
||||||
|
return y >= minY && y <= maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ore placed at a Y inside {@link #contains(int)}, or null when no generator places one.
|
||||||
|
*/
|
||||||
|
public NativeBlockState generate(int x, int y, int z, RNG rng, IrisData data) {
|
||||||
|
IrisOreGenerator[] candidates = byY == null ? generators : byY[y - minY];
|
||||||
|
for (IrisOreGenerator candidate : candidates) {
|
||||||
|
NativeBlockState ore = candidate.generate(x, y, z, rng, data);
|
||||||
|
if (ore != null) {
|
||||||
|
return ore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,10 +55,10 @@ public class IrisOreGenerator {
|
|||||||
@Description("Vertical band (min, max) this ore can generate in, in engine-local Y where 0 is the bottom of the dimension, not world Y.")
|
@Description("Vertical band (min, max) this ore can generate in, in engine-local Y where 0 is the bottom of the dimension, not world Y.")
|
||||||
private IrisRange range = new IrisRange(30, 80);
|
private IrisRange range = new IrisRange(30, 80);
|
||||||
|
|
||||||
private transient AtomicCache<CNG> chanceCache = new AtomicCache<>();
|
private transient AtomicCache<OreChance> chanceCache = new AtomicCache<>();
|
||||||
|
|
||||||
public void warm(RNG rng, IrisData data) {
|
public void warm(RNG rng, IrisData data) {
|
||||||
chanceCache.aquire(() -> chanceStyle.create(rng, data));
|
chanceCache.aquire(() -> OreChance.of(chanceStyle.create(rng, data)));
|
||||||
palette.getLayerGenerator(rng, data);
|
palette.getLayerGenerator(rng, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,15 +71,30 @@ public class IrisOreGenerator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CNG chance = chanceCache.getIfPresent();
|
OreChance chance = chanceCache.getIfPresent();
|
||||||
if (chance == null) {
|
if (chance == null) {
|
||||||
chance = chanceCache.aquire(() -> chanceStyle.create(rng, data));
|
chance = chanceCache.aquire(() -> OreChance.of(chanceStyle.create(rng, data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chance.noise(x, y, z) > threshold) {
|
if (chance.sample(x, y, z) > threshold) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return palette.get(rng, x, y, z, data);
|
return palette.get(rng, x, y, z, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The chance field, with its single value resolved once when it is constant (a FLAT style),
|
||||||
|
* which bands such as a full deepslate layer sample at every block.
|
||||||
|
*/
|
||||||
|
private record OreChance(CNG noise, boolean constant, double value) {
|
||||||
|
private static OreChance of(CNG noise) {
|
||||||
|
boolean constant = noise.isConstant();
|
||||||
|
return new OreChance(noise, constant, constant ? noise.noise(0D, 0D, 0D) : 0D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double sample(int x, int y, int z) {
|
||||||
|
return constant ? value : noise.noise(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package art.arcane.iris.generation.decoration;
|
|
||||||
|
|
||||||
import art.arcane.iris.pack.value.IrisRange;
|
|
||||||
|
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
|
||||||
|
|
||||||
public final class IrisOreGeneratorBounds {
|
|
||||||
public static final IrisOreGeneratorBounds EMPTY = new IrisOreGeneratorBounds(false, 0D, 0D);
|
|
||||||
|
|
||||||
private final boolean hasOres;
|
|
||||||
private final double min;
|
|
||||||
private final double max;
|
|
||||||
|
|
||||||
private IrisOreGeneratorBounds(boolean hasOres, double min, double max) {
|
|
||||||
this.hasOres = hasOres;
|
|
||||||
this.min = min;
|
|
||||||
this.max = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IrisOreGeneratorBounds of(KList<IrisOreGenerator> ores) {
|
|
||||||
if (ores == null || ores.isEmpty()) {
|
|
||||||
return EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
double min = Double.POSITIVE_INFINITY;
|
|
||||||
double max = Double.NEGATIVE_INFINITY;
|
|
||||||
int oreCount = ores.size();
|
|
||||||
for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
|
|
||||||
IrisOreGenerator oreGenerator = ores.get(oreIndex);
|
|
||||||
IrisRange range = oreGenerator.getRange();
|
|
||||||
min = Math.min(min, range.getMin());
|
|
||||||
max = Math.max(max, range.getMax());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IrisOreGeneratorBounds(true, min, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasOres() {
|
|
||||||
return hasOres;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean contains(int y) {
|
|
||||||
return hasOres && y >= min && y <= max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.block.B;
|
||||||
|
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards a chunk's block writes to {@link BlockUpdater#catchBlockUpdates} at world coordinates.
|
||||||
|
* The last state seen to need neither a block update nor custom handling is remembered, so the
|
||||||
|
* long runs of plain terrain skip the per-block checks. Racing writers only ever store such states.
|
||||||
|
*/
|
||||||
|
final class ChunkBlockUpdateListener implements ColumnExtentListeningHunk.Listener<NativeBlockState> {
|
||||||
|
private final BlockUpdater updater;
|
||||||
|
private final int originX;
|
||||||
|
private final int originZ;
|
||||||
|
private NativeBlockState quiet;
|
||||||
|
|
||||||
|
ChunkBlockUpdateListener(BlockUpdater updater, int originX, int originZ) {
|
||||||
|
this.updater = updater;
|
||||||
|
this.originX = originX;
|
||||||
|
this.originZ = originZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWrite(int x, int y, int z, NativeBlockState state) {
|
||||||
|
if (state == null || state == quiet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (B.isUpdatable(state) || state.isCustom()) {
|
||||||
|
updater.catchBlockUpdates(originX + x, y, originZ + z, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
quiet = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1491,7 +1491,7 @@ public class IrisEngine implements Engine {
|
|||||||
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
|
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
|
||||||
getEngineData().getStatistics().generatedChunk();
|
getEngineData().getStatistics().generatedChunk();
|
||||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||||
Hunk<NativeBlockState> blocks = new ColumnExtentListeningHunk<>(vblocks, (xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t));
|
Hunk<NativeBlockState> blocks = new ColumnExtentListeningHunk<>(vblocks, new ChunkBlockUpdateListener(this, x, z));
|
||||||
|
|
||||||
if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) {
|
if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) {
|
||||||
NativeBlockState crossSection = B.getState("CRYING_OBSIDIAN");
|
NativeBlockState crossSection = B.getState("CRYING_OBSIDIAN");
|
||||||
|
|||||||
+310
-264
@@ -30,8 +30,7 @@ import art.arcane.iris.generation.biome.IrisBiome;
|
|||||||
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
||||||
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBedConfig;
|
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBedConfig;
|
||||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
import art.arcane.iris.generation.decoration.IrisOreBands;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGeneratorBounds;
|
|
||||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||||
import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
|
import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
|
||||||
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBankConfig;
|
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBankConfig;
|
||||||
@@ -52,8 +51,6 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
|
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
|
||||||
@Getter
|
@Getter
|
||||||
private final RNG rng;
|
private final RNG rng;
|
||||||
@Getter
|
|
||||||
private int lastBedrock = -1;
|
|
||||||
|
|
||||||
public IrisTerrainNormalActuator(Engine engine) {
|
public IrisTerrainNormalActuator(Engine engine) {
|
||||||
super(engine, "Terrain");
|
super(engine, "Terrain");
|
||||||
@@ -64,263 +61,25 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
@Override
|
@Override
|
||||||
public void onActuate(int x, int z, Hunk<NativeBlockState> h, boolean multicore, ChunkContext context) {
|
public void onActuate(int x, int z, Hunk<NativeBlockState> h, boolean multicore, ChunkContext context) {
|
||||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||||
|
paint(x, z, h, context);
|
||||||
for (int xf = 0; xf < h.getWidth(); xf++) {
|
|
||||||
terrainSliver(x, z, xf, h, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
getEngine().getMetrics().getTerrain().put(p.getMilliseconds());
|
getEngine().getMetrics().getTerrain().put(p.getMilliseconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is calling 1/16th of a chunk x/z slice. It is a plane from sky to bedrock 1 thick in the x direction.
|
* Paints every column of the chunk from its top down to bedrock. Columns are walked z-major so
|
||||||
|
* consecutive columns share the cache lines of the x-minor block buffer.
|
||||||
*
|
*
|
||||||
* @param x the chunk x in blocks
|
* @param x the chunk x in blocks
|
||||||
* @param z the chunk z in blocks
|
* @param z the chunk z in blocks
|
||||||
* @param xf the current x slice
|
|
||||||
* @param h the blockdata
|
|
||||||
*/
|
*/
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
public void terrainSliver(int x, int z, int xf, Hunk<NativeBlockState> h, ChunkContext context) {
|
public void paint(int x, int z, Hunk<NativeBlockState> h, ChunkContext context) {
|
||||||
int chunkHeight = h.getHeight();
|
ColumnPainter painter = new ColumnPainter(x, z, h, context);
|
||||||
int chunkDepth = h.getDepth();
|
int width = h.getWidth();
|
||||||
IrisDimension dimension = getDimension();
|
int depth = h.getDepth();
|
||||||
IrisData data = getData();
|
for (int zf = 0; zf < depth; zf++) {
|
||||||
IrisComplex complex = getComplex();
|
for (int xf = 0; xf < width; xf++) {
|
||||||
ProceduralStream<Double> riverWaterSurfaceStream = complex.getRiverWaterSurfaceStream();
|
painter.paint(xf, zf);
|
||||||
RNG localRng = rng;
|
|
||||||
boolean bedrockEnabled = dimension.isBedrock();
|
|
||||||
boolean hideOres = dimension.isHideOresForHiddenOre();
|
|
||||||
ChunkedDataCache<IrisBiome> biomeCache = context.getBiome();
|
|
||||||
ChunkedDataCache<IrisRegion> regionCache = context.getRegion();
|
|
||||||
ChunkedDataCache<NativeBlockState> rockCache = context.getRock();
|
|
||||||
int realX = xf + x;
|
|
||||||
UpperDimensionContext upperContext = getEngine().getUpperContext();
|
|
||||||
// Dimension-level ore lookups are chunk-invariant; resolving them per column paid
|
|
||||||
// four accessor chains times 256 per chunk for constants.
|
|
||||||
KList<IrisOreGenerator> dimensionSurfaceOres = hideOres ? null : dimension.getSurfaceOreGenerators();
|
|
||||||
KList<IrisOreGenerator> dimensionUndergroundOres = hideOres ? null : dimension.getUndergroundOreGenerators();
|
|
||||||
IrisOreGeneratorBounds dimensionSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : dimension.getSurfaceOreGeneratorBounds();
|
|
||||||
IrisOreGeneratorBounds dimensionUndergroundOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : dimension.getUndergroundOreGeneratorBounds();
|
|
||||||
IrisSurfaceRiverBankConfig riverBanks = dimension.getHydrology() == null
|
|
||||||
? null
|
|
||||||
: dimension.getHydrology().getRivers().getSurface().getBanks();
|
|
||||||
boolean exposeCutStrata = riverBanks != null && riverBanks.isExposeCutStrata();
|
|
||||||
IrisSurfaceRiverBedConfig riverBed = dimension.getHydrology() == null
|
|
||||||
? null
|
|
||||||
: dimension.getHydrology().getRivers().getSurface().getBed();
|
|
||||||
boolean padRiverBed = riverBed != null && !riverBed.isAllowGravityBlocks();
|
|
||||||
IrisRiverMaterialConfig bedMaterial = riverBed == null ? null : riverBed.getMaterial();
|
|
||||||
IrisRiverMaterialConfig shoreMaterial = riverBanks == null ? null : riverBanks.getShoreMaterial();
|
|
||||||
IrisRiverMaterialConfig bankMaterial = riverBanks == null ? null : riverBanks.getBankMaterial();
|
|
||||||
|
|
||||||
for (int zf = 0; zf < chunkDepth; zf++) {
|
|
||||||
int realZ = zf + z;
|
|
||||||
IrisBiome biome = biomeCache.get(xf, zf);
|
|
||||||
IrisRegion region = regionCache.get(xf, zf);
|
|
||||||
int he = Math.min(chunkHeight, context.getRoundedHeight(xf, zf));
|
|
||||||
int surfaceFluidHeight = Math.min(
|
|
||||||
chunkHeight,
|
|
||||||
(int) Math.round(riverWaterSurfaceStream.getDouble(realX, realZ))
|
|
||||||
);
|
|
||||||
int hf = Math.max(surfaceFluidHeight, he);
|
|
||||||
if (hf < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int topY = Math.min(hf, chunkHeight - 1);
|
|
||||||
HydrologyColumnSample hydrology = complex.sampleHydrologyColumn(realX, realZ);
|
|
||||||
HydrologyColumnLayer hydrologyFluid = hydrology == null
|
|
||||||
? null
|
|
||||||
: hydrology.primarySurfaceFluidLayerOrNull();
|
|
||||||
HydrologyColumnLayer hydrologyTerrain = hydrology == null
|
|
||||||
? null
|
|
||||||
: hydrology.primarySurfaceLayerOrNull();
|
|
||||||
Terrain3DColumn terrainColumn = complex.terrainColumn(realX, realZ, hydrology);
|
|
||||||
int terrainSpan = terrainColumn == null ? -1 : terrainColumn.spanCount() - 1;
|
|
||||||
int layerSurfaceY = he;
|
|
||||||
int layerCeilingY = 0;
|
|
||||||
boolean exposeRiverStrata = exposeCutStrata
|
|
||||||
&& hydrologyTerrain != null
|
|
||||||
&& hydrologyTerrain.terrainOwned()
|
|
||||||
&& !hydrologyTerrain.channel();
|
|
||||||
Terrain3DColumn naturalColumn = exposeRiverStrata ? complex.naturalTerrainColumn(realX, realZ) : null;
|
|
||||||
int cut = 0;
|
|
||||||
boolean riverOwned = padRiverBed && hydrologyTerrain != null && hydrologyTerrain.terrainOwned();
|
|
||||||
IrisRiverMaterialConfig roleMaterial = hydrologyRoleMaterial(
|
|
||||||
hydrologyTerrain, bedMaterial, shoreMaterial, bankMaterial);
|
|
||||||
NativeBlockState fluid = hydrologyFluid == null || complex.getTransitionDisplacement() != null
|
|
||||||
? complex.resolveSurfaceFluid(realX, realZ)
|
|
||||||
: complex.resolveHydrologyFluid(hydrologyFluid.profileKey(), realX, realZ);
|
|
||||||
NativeBlockState rock = rockCache.get(xf, zf);
|
|
||||||
NativeBlockState mappedSurfaceBlock = complex.getImageMapRuntime().sampleSurfaceBlock(realX, realZ);
|
|
||||||
KList<IrisOreGenerator> biomeSurfaceOres = hideOres ? null : biome.getSurfaceOreGenerators();
|
|
||||||
KList<IrisOreGenerator> regionSurfaceOres = hideOres ? null : region.getSurfaceOreGenerators();
|
|
||||||
KList<IrisOreGenerator> biomeUndergroundOres = hideOres ? null : biome.getUndergroundOreGenerators();
|
|
||||||
KList<IrisOreGenerator> regionUndergroundOres = hideOres ? null : region.getUndergroundOreGenerators();
|
|
||||||
IrisOreGeneratorBounds biomeSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : biome.getSurfaceOreGeneratorBounds();
|
|
||||||
IrisOreGeneratorBounds regionSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : region.getSurfaceOreGeneratorBounds();
|
|
||||||
IrisOreGeneratorBounds biomeUndergroundOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : biome.getUndergroundOreGeneratorBounds();
|
|
||||||
IrisOreGeneratorBounds regionUndergroundOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : region.getUndergroundOreGeneratorBounds();
|
|
||||||
boolean hasSurfaceOres = biomeSurfaceOreBounds.hasOres() || regionSurfaceOreBounds.hasOres() || dimensionSurfaceOreBounds.hasOres();
|
|
||||||
boolean hasUndergroundOres = biomeUndergroundOreBounds.hasOres() || regionUndergroundOreBounds.hasOres() || dimensionUndergroundOreBounds.hasOres();
|
|
||||||
KList<NativeBlockState> blocks = null;
|
|
||||||
KList<NativeBlockState> ceilingBlocks = null;
|
|
||||||
KList<NativeBlockState> fblocks = null;
|
|
||||||
|
|
||||||
for (int i = topY; i >= 0; i--) {
|
|
||||||
if (i == 0 && bedrockEnabled) {
|
|
||||||
h.setRaw(xf, i, zf, BEDROCK.get());
|
|
||||||
lastBedrock = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (terrainColumn != null && i <= he) {
|
|
||||||
while (terrainSpan >= 0 && i < terrainColumn.ceiling(terrainSpan)) {
|
|
||||||
terrainSpan--;
|
|
||||||
blocks = null;
|
|
||||||
ceilingBlocks = null;
|
|
||||||
}
|
|
||||||
if (terrainSpan < 0 || i > terrainColumn.floor(terrainSpan)) {
|
|
||||||
h.setRaw(xf, i, zf, AIR.get());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
layerSurfaceY = terrainColumn.floor(terrainSpan);
|
|
||||||
layerCeilingY = terrainColumn.ceiling(terrainSpan);
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBlockState ore = null;
|
|
||||||
if (hasSurfaceOres) {
|
|
||||||
if (biomeSurfaceOreBounds.contains(i)) {
|
|
||||||
ore = generateOres(biomeSurfaceOres, realX, i, realZ, localRng, data);
|
|
||||||
}
|
|
||||||
if (ore == null && regionSurfaceOreBounds.contains(i)) {
|
|
||||||
ore = generateOres(regionSurfaceOres, realX, i, realZ, localRng, data);
|
|
||||||
}
|
|
||||||
if (ore == null && dimensionSurfaceOreBounds.contains(i)) {
|
|
||||||
ore = generateOres(dimensionSurfaceOres, realX, i, realZ, localRng, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ore != null) {
|
|
||||||
h.setRaw(xf, i, zf, ore);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i > he && i <= hf) {
|
|
||||||
int fdepth = hf - i;
|
|
||||||
if (hydrologyFluid == null && fblocks == null) {
|
|
||||||
fblocks = biome.generateSeaLayers(realX, realZ, localRng, hf - he, data);
|
|
||||||
}
|
|
||||||
h.setRaw(xf, i, zf, HydrologyFluidLayerSelector.select(
|
|
||||||
fblocks,
|
|
||||||
fdepth,
|
|
||||||
fluid,
|
|
||||||
hydrologyFluid != null
|
|
||||||
));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i <= he) {
|
|
||||||
int depth = layerSurfaceY - i;
|
|
||||||
if (depth == 0 && mappedSurfaceBlock != null) {
|
|
||||||
h.setRaw(xf, i, zf, mappedSurfaceBlock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (blocks == null) {
|
|
||||||
if (exposeRiverStrata) {
|
|
||||||
int naturalSurfaceY = naturalColumn == null ? hydrology.naturalHeight() : naturalColumn.surfaceY(layerSurfaceY);
|
|
||||||
cut = Math.max(0, naturalSurfaceY - layerSurfaceY);
|
|
||||||
}
|
|
||||||
blocks = biome.generateLayers(dimension, realX, realZ, localRng,
|
|
||||||
layerSurfaceY + cut, layerSurfaceY + cut, data, complex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layerCeilingY > 0 && depth >= 2 && i - layerCeilingY < 2) {
|
|
||||||
if (ceilingBlocks == null) {
|
|
||||||
ceilingBlocks = biome.generateCeilingLayers(dimension, realX, realZ, localRng,
|
|
||||||
2, layerCeilingY, data, complex);
|
|
||||||
}
|
|
||||||
int ceilingDepth = i - layerCeilingY;
|
|
||||||
h.setRaw(xf, i, zf, ceilingBlocks.hasIndex(ceilingDepth)
|
|
||||||
? ceilingBlocks.get(ceilingDepth) : rock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int strataIndex = strataIndex(depth, cut, blocks.size());
|
|
||||||
NativeBlockState layerBlock = paintHydrologyMaterial(
|
|
||||||
blocks.hasIndex(strataIndex) ? blocks.get(strataIndex) : null,
|
|
||||||
roleMaterial, depth, localRng, realX, i, realZ, data);
|
|
||||||
if (layerBlock != null) {
|
|
||||||
if (riverOwned && depth <= riverBed.getPadding() && IrisProceduralBlocks.isGravityAffected(layerBlock)) {
|
|
||||||
layerBlock = riverBed.getPaddingPalette().get(localRng, realX, i, realZ, data);
|
|
||||||
}
|
|
||||||
h.setRaw(xf, i, zf, layerBlock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasUndergroundOres) {
|
|
||||||
if (biomeUndergroundOreBounds.contains(i)) {
|
|
||||||
ore = generateOres(biomeUndergroundOres, realX, i, realZ, localRng, data);
|
|
||||||
}
|
|
||||||
if (ore == null && regionUndergroundOreBounds.contains(i)) {
|
|
||||||
ore = generateOres(regionUndergroundOres, realX, i, realZ, localRng, data);
|
|
||||||
}
|
|
||||||
if (ore == null && dimensionUndergroundOreBounds.contains(i)) {
|
|
||||||
ore = generateOres(dimensionUndergroundOres, realX, i, realZ, localRng, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ore != null) {
|
|
||||||
h.setRaw(xf, i, zf, ore);
|
|
||||||
} else {
|
|
||||||
h.setRaw(xf, i, zf, rock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (upperContext != null) {
|
|
||||||
UpperDimensionContext.Column upperColumn = upperContext.sampleColumn(realX, realZ);
|
|
||||||
int upperSurfaceY = upperColumn.surfaceY();
|
|
||||||
|
|
||||||
if (upperSurfaceY < chunkHeight - 1) {
|
|
||||||
IrisBiome upperBiome = upperContext.getUpperBiome(realX, realZ);
|
|
||||||
NativeBlockState upperRock = upperContext.getRockBlock(realX, realZ);
|
|
||||||
NativeBlockState upperMappedSurface = upperContext.getSurfaceBlock(realX, realZ);
|
|
||||||
KList<NativeBlockState> upperBlocks = null;
|
|
||||||
int paletteSourceY = -1;
|
|
||||||
|
|
||||||
for (int y = chunkHeight - 1; y >= upperSurfaceY; y--) {
|
|
||||||
if (y == chunkHeight - 1 && bedrockEnabled) {
|
|
||||||
h.setRaw(xf, y, zf, BEDROCK.get());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!upperColumn.isSolid(y)) {
|
|
||||||
h.setRaw(xf, y, zf, AIR.get());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int faceY = upperColumn.faceY(y);
|
|
||||||
int sourceSurfaceY = upperColumn.height() - 1 - faceY;
|
|
||||||
if (sourceSurfaceY != paletteSourceY) {
|
|
||||||
paletteSourceY = sourceSurfaceY;
|
|
||||||
upperBlocks = null;
|
|
||||||
}
|
|
||||||
if (y == faceY && upperMappedSurface != null) {
|
|
||||||
h.setRaw(xf, y, zf, upperMappedSurface);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int depthFromFace = y - faceY;
|
|
||||||
if (upperBlocks == null && upperBiome != null) {
|
|
||||||
upperBlocks = upperBiome.generateLayersWithSlope(upperContext.getDimension(),
|
|
||||||
realX, realZ, localRng, sourceSurfaceY, sourceSurfaceY,
|
|
||||||
upperContext.getData(), upperContext.getSurfaceSlopeStream(sourceSurfaceY));
|
|
||||||
}
|
|
||||||
if (upperBlocks != null && upperBlocks.hasIndex(depthFromFace)) {
|
|
||||||
h.setRaw(xf, y, zf, upperBlocks.get(depthFromFace));
|
|
||||||
} else {
|
|
||||||
h.setRaw(xf, y, zf, upperRock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,20 +144,307 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<NativeBloc
|
|||||||
return painted == null ? layerBlock : painted;
|
return painted == null ? layerBlock : painted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NativeBlockState generateOres(KList<IrisOreGenerator> oreGenerators, int x, int y, int z, RNG rng, IrisData data) {
|
private final class ColumnPainter {
|
||||||
if (oreGenerators == null || oreGenerators.isEmpty()) {
|
private final int x;
|
||||||
return null;
|
private final int z;
|
||||||
|
private final Hunk<NativeBlockState> h;
|
||||||
|
private final ChunkContext context;
|
||||||
|
private final int chunkHeight;
|
||||||
|
private final IrisDimension dimension;
|
||||||
|
private final IrisData data;
|
||||||
|
private final IrisComplex complex;
|
||||||
|
private final ProceduralStream<Double> riverWaterSurfaceStream;
|
||||||
|
private final boolean bedrockEnabled;
|
||||||
|
private final boolean hideOres;
|
||||||
|
private final boolean transitionDisplaced;
|
||||||
|
private final ChunkedDataCache<IrisBiome> biomeCache;
|
||||||
|
private final ChunkedDataCache<IrisRegion> regionCache;
|
||||||
|
private final ChunkedDataCache<NativeBlockState> rockCache;
|
||||||
|
private final UpperDimensionContext upperContext;
|
||||||
|
private final IrisOreBands dimensionSurfaceOres;
|
||||||
|
private final IrisOreBands dimensionUndergroundOres;
|
||||||
|
private final boolean exposeCutStrata;
|
||||||
|
private final IrisSurfaceRiverBedConfig riverBed;
|
||||||
|
private final boolean padRiverBed;
|
||||||
|
private final IrisRiverMaterialConfig bedMaterial;
|
||||||
|
private final IrisRiverMaterialConfig shoreMaterial;
|
||||||
|
private final IrisRiverMaterialConfig bankMaterial;
|
||||||
|
private final NativeBlockState air;
|
||||||
|
private final NativeBlockState bedrock;
|
||||||
|
private IrisOreBands biomeSurfaceOres;
|
||||||
|
private IrisOreBands regionSurfaceOres;
|
||||||
|
private IrisOreBands biomeUndergroundOres;
|
||||||
|
private IrisOreBands regionUndergroundOres;
|
||||||
|
|
||||||
|
private ColumnPainter(int x, int z, Hunk<NativeBlockState> h, ChunkContext context) {
|
||||||
|
this.x = x;
|
||||||
|
this.z = z;
|
||||||
|
this.h = h;
|
||||||
|
this.context = context;
|
||||||
|
chunkHeight = h.getHeight();
|
||||||
|
dimension = getDimension();
|
||||||
|
data = getData();
|
||||||
|
complex = getComplex();
|
||||||
|
riverWaterSurfaceStream = complex.getRiverWaterSurfaceStream();
|
||||||
|
bedrockEnabled = dimension.isBedrock();
|
||||||
|
hideOres = dimension.isHideOresForHiddenOre();
|
||||||
|
transitionDisplaced = complex.getTransitionDisplacement() != null;
|
||||||
|
biomeCache = context.getBiome();
|
||||||
|
regionCache = context.getRegion();
|
||||||
|
rockCache = context.getRock();
|
||||||
|
upperContext = getEngine().getUpperContext();
|
||||||
|
dimensionSurfaceOres = hideOres ? IrisOreBands.EMPTY : dimension.getSurfaceOreBands();
|
||||||
|
dimensionUndergroundOres = hideOres ? IrisOreBands.EMPTY : dimension.getUndergroundOreBands();
|
||||||
|
IrisSurfaceRiverBankConfig riverBanks = dimension.getHydrology() == null
|
||||||
|
? null
|
||||||
|
: dimension.getHydrology().getRivers().getSurface().getBanks();
|
||||||
|
exposeCutStrata = riverBanks != null && riverBanks.isExposeCutStrata();
|
||||||
|
riverBed = dimension.getHydrology() == null
|
||||||
|
? null
|
||||||
|
: dimension.getHydrology().getRivers().getSurface().getBed();
|
||||||
|
padRiverBed = riverBed != null && !riverBed.isAllowGravityBlocks();
|
||||||
|
bedMaterial = riverBed == null ? null : riverBed.getMaterial();
|
||||||
|
shoreMaterial = riverBanks == null ? null : riverBanks.getShoreMaterial();
|
||||||
|
bankMaterial = riverBanks == null ? null : riverBanks.getBankMaterial();
|
||||||
|
air = AIR.get();
|
||||||
|
bedrock = BEDROCK.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
int oreCount = oreGenerators.size();
|
private void paint(int xf, int zf) {
|
||||||
for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
|
int realX = xf + x;
|
||||||
IrisOreGenerator oreGenerator = oreGenerators.get(oreIndex);
|
int realZ = zf + z;
|
||||||
NativeBlockState ore = oreGenerator.generate(x, y, z, rng, data);
|
IrisBiome biome = biomeCache.get(xf, zf);
|
||||||
if (ore != null) {
|
IrisRegion region = regionCache.get(xf, zf);
|
||||||
return ore;
|
int he = Math.min(chunkHeight, context.getRoundedHeight(xf, zf));
|
||||||
|
int surfaceFluidHeight = Math.min(
|
||||||
|
chunkHeight,
|
||||||
|
(int) Math.round(riverWaterSurfaceStream.getDouble(realX, realZ))
|
||||||
|
);
|
||||||
|
int hf = Math.max(surfaceFluidHeight, he);
|
||||||
|
if (hf < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int topY = Math.min(hf, chunkHeight - 1);
|
||||||
|
HydrologyColumnSample hydrology = complex.sampleHydrologyColumn(realX, realZ);
|
||||||
|
HydrologyColumnLayer hydrologyFluid = hydrology == null
|
||||||
|
? null
|
||||||
|
: hydrology.primarySurfaceFluidLayerOrNull();
|
||||||
|
HydrologyColumnLayer hydrologyTerrain = hydrology == null
|
||||||
|
? null
|
||||||
|
: hydrology.primarySurfaceLayerOrNull();
|
||||||
|
Terrain3DColumn terrainColumn = complex.terrainColumn(realX, realZ, hydrology);
|
||||||
|
int terrainSpan = terrainColumn == null ? -1 : terrainColumn.spanCount() - 1;
|
||||||
|
int layerSurfaceY = he;
|
||||||
|
int layerCeilingY = 0;
|
||||||
|
boolean exposeRiverStrata = exposeCutStrata
|
||||||
|
&& hydrologyTerrain != null
|
||||||
|
&& hydrologyTerrain.terrainOwned()
|
||||||
|
&& !hydrologyTerrain.channel();
|
||||||
|
Terrain3DColumn naturalColumn = exposeRiverStrata ? complex.naturalTerrainColumn(realX, realZ) : null;
|
||||||
|
int cut = 0;
|
||||||
|
boolean riverOwned = padRiverBed && hydrologyTerrain != null && hydrologyTerrain.terrainOwned();
|
||||||
|
IrisRiverMaterialConfig roleMaterial = hydrologyRoleMaterial(
|
||||||
|
hydrologyTerrain, bedMaterial, shoreMaterial, bankMaterial);
|
||||||
|
int paintDepth = roleMaterial == null ? 0 : roleMaterial.getDepth();
|
||||||
|
NativeBlockState fluid = hydrologyFluid == null || transitionDisplaced
|
||||||
|
? complex.resolveSurfaceFluid(realX, realZ)
|
||||||
|
: complex.resolveHydrologyFluid(hydrologyFluid.profileKey(), realX, realZ);
|
||||||
|
NativeBlockState rock = rockCache.get(xf, zf);
|
||||||
|
NativeBlockState mappedSurfaceBlock = complex.getImageMapRuntime().sampleSurfaceBlock(realX, realZ);
|
||||||
|
biomeSurfaceOres = hideOres ? IrisOreBands.EMPTY : biome.getSurfaceOreBands();
|
||||||
|
regionSurfaceOres = hideOres ? IrisOreBands.EMPTY : region.getSurfaceOreBands();
|
||||||
|
biomeUndergroundOres = hideOres ? IrisOreBands.EMPTY : biome.getUndergroundOreBands();
|
||||||
|
regionUndergroundOres = hideOres ? IrisOreBands.EMPTY : region.getUndergroundOreBands();
|
||||||
|
boolean hasSurfaceOres = biomeSurfaceOres.hasOres() || regionSurfaceOres.hasOres() || dimensionSurfaceOres.hasOres();
|
||||||
|
boolean hasUndergroundOres = biomeUndergroundOres.hasOres() || regionUndergroundOres.hasOres() || dimensionUndergroundOres.hasOres();
|
||||||
|
int bedrockFloor = bedrockEnabled ? 1 : 0;
|
||||||
|
KList<NativeBlockState> blocks = null;
|
||||||
|
KList<NativeBlockState> ceilingBlocks = null;
|
||||||
|
KList<NativeBlockState> fblocks = null;
|
||||||
|
|
||||||
|
for (int i = topY; i >= 0; i--) {
|
||||||
|
if (i == 0 && bedrockEnabled) {
|
||||||
|
h.setRaw(xf, i, zf, bedrock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terrainColumn != null && i <= he) {
|
||||||
|
while (terrainSpan >= 0 && i < terrainColumn.ceiling(terrainSpan)) {
|
||||||
|
terrainSpan--;
|
||||||
|
blocks = null;
|
||||||
|
ceilingBlocks = null;
|
||||||
|
}
|
||||||
|
if (terrainSpan < 0 || i > terrainColumn.floor(terrainSpan)) {
|
||||||
|
h.setRaw(xf, i, zf, air);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
layerSurfaceY = terrainColumn.floor(terrainSpan);
|
||||||
|
layerCeilingY = terrainColumn.ceiling(terrainSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeBlockState ore = hasSurfaceOres ? surfaceOre(realX, i, realZ) : null;
|
||||||
|
if (ore != null) {
|
||||||
|
h.setRaw(xf, i, zf, ore);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > he && i <= hf) {
|
||||||
|
int fdepth = hf - i;
|
||||||
|
if (hydrologyFluid == null && fblocks == null) {
|
||||||
|
fblocks = biome.generateSeaLayers(realX, realZ, rng, hf - he, data);
|
||||||
|
}
|
||||||
|
h.setRaw(xf, i, zf, HydrologyFluidLayerSelector.select(
|
||||||
|
fblocks,
|
||||||
|
fdepth,
|
||||||
|
fluid,
|
||||||
|
hydrologyFluid != null
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i <= he) {
|
||||||
|
int depth = layerSurfaceY - i;
|
||||||
|
if (depth == 0 && mappedSurfaceBlock != null) {
|
||||||
|
h.setRaw(xf, i, zf, mappedSurfaceBlock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (blocks == null) {
|
||||||
|
if (exposeRiverStrata) {
|
||||||
|
int naturalSurfaceY = naturalColumn == null ? hydrology.naturalHeight() : naturalColumn.surfaceY(layerSurfaceY);
|
||||||
|
cut = Math.max(0, naturalSurfaceY - layerSurfaceY);
|
||||||
|
}
|
||||||
|
blocks = biome.generateLayers(dimension, realX, realZ, rng,
|
||||||
|
layerSurfaceY + cut, layerSurfaceY + cut, data, complex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int deepFloor = Math.max(bedrockFloor, layerCeilingY > 0 ? layerCeilingY + 2 : 0);
|
||||||
|
if (i >= deepFloor && depth >= Math.max(blocks.size(), paintDepth)) {
|
||||||
|
paintDeep(xf, zf, realX, realZ, i, deepFloor, rock, hasSurfaceOres, hasUndergroundOres);
|
||||||
|
i = deepFloor;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layerCeilingY > 0 && depth >= 2 && i - layerCeilingY < 2) {
|
||||||
|
if (ceilingBlocks == null) {
|
||||||
|
ceilingBlocks = biome.generateCeilingLayers(dimension, realX, realZ, rng,
|
||||||
|
2, layerCeilingY, data, complex);
|
||||||
|
}
|
||||||
|
int ceilingDepth = i - layerCeilingY;
|
||||||
|
h.setRaw(xf, i, zf, ceilingBlocks.hasIndex(ceilingDepth)
|
||||||
|
? ceilingBlocks.get(ceilingDepth) : rock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strataIndex = strataIndex(depth, cut, blocks.size());
|
||||||
|
NativeBlockState layerBlock = paintHydrologyMaterial(
|
||||||
|
blocks.hasIndex(strataIndex) ? blocks.get(strataIndex) : null,
|
||||||
|
roleMaterial, depth, rng, realX, i, realZ, data);
|
||||||
|
if (layerBlock != null) {
|
||||||
|
if (riverOwned && depth <= riverBed.getPadding() && IrisProceduralBlocks.isGravityAffected(layerBlock)) {
|
||||||
|
layerBlock = riverBed.getPaddingPalette().get(rng, realX, i, realZ, data);
|
||||||
|
}
|
||||||
|
h.setRaw(xf, i, zf, layerBlock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ore = hasUndergroundOres ? undergroundOre(realX, i, realZ) : null;
|
||||||
|
h.setRaw(xf, i, zf, ore != null ? ore : rock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upperContext != null) {
|
||||||
|
paintUpper(xf, zf, realX, realZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
/**
|
||||||
|
* Paints the solid run from {@code top} down to {@code floor}, which lies below every biome
|
||||||
|
* layer, river material, mapped surface and cave ceiling of its span: only ores or rock land there.
|
||||||
|
*/
|
||||||
|
private void paintDeep(int xf, int zf, int realX, int realZ, int top, int floor, NativeBlockState rock,
|
||||||
|
boolean hasSurfaceOres, boolean hasUndergroundOres) {
|
||||||
|
for (int y = top; y >= floor; y--) {
|
||||||
|
NativeBlockState ore = hasSurfaceOres ? surfaceOre(realX, y, realZ) : null;
|
||||||
|
if (ore == null && hasUndergroundOres) {
|
||||||
|
ore = undergroundOre(realX, y, realZ);
|
||||||
|
}
|
||||||
|
h.setRaw(xf, y, zf, ore != null ? ore : rock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NativeBlockState surfaceOre(int realX, int y, int realZ) {
|
||||||
|
NativeBlockState ore = null;
|
||||||
|
if (biomeSurfaceOres.contains(y)) {
|
||||||
|
ore = biomeSurfaceOres.generate(realX, y, realZ, rng, data);
|
||||||
|
}
|
||||||
|
if (ore == null && regionSurfaceOres.contains(y)) {
|
||||||
|
ore = regionSurfaceOres.generate(realX, y, realZ, rng, data);
|
||||||
|
}
|
||||||
|
if (ore == null && dimensionSurfaceOres.contains(y)) {
|
||||||
|
ore = dimensionSurfaceOres.generate(realX, y, realZ, rng, data);
|
||||||
|
}
|
||||||
|
return ore;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NativeBlockState undergroundOre(int realX, int y, int realZ) {
|
||||||
|
NativeBlockState ore = null;
|
||||||
|
if (biomeUndergroundOres.contains(y)) {
|
||||||
|
ore = biomeUndergroundOres.generate(realX, y, realZ, rng, data);
|
||||||
|
}
|
||||||
|
if (ore == null && regionUndergroundOres.contains(y)) {
|
||||||
|
ore = regionUndergroundOres.generate(realX, y, realZ, rng, data);
|
||||||
|
}
|
||||||
|
if (ore == null && dimensionUndergroundOres.contains(y)) {
|
||||||
|
ore = dimensionUndergroundOres.generate(realX, y, realZ, rng, data);
|
||||||
|
}
|
||||||
|
return ore;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintUpper(int xf, int zf, int realX, int realZ) {
|
||||||
|
UpperDimensionContext.Column upperColumn = upperContext.sampleColumn(realX, realZ);
|
||||||
|
int upperSurfaceY = upperColumn.surfaceY();
|
||||||
|
if (upperSurfaceY >= chunkHeight - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisBiome upperBiome = upperContext.getUpperBiome(realX, realZ);
|
||||||
|
NativeBlockState upperRock = upperContext.getRockBlock(realX, realZ);
|
||||||
|
NativeBlockState upperMappedSurface = upperContext.getSurfaceBlock(realX, realZ);
|
||||||
|
KList<NativeBlockState> upperBlocks = null;
|
||||||
|
int paletteSourceY = -1;
|
||||||
|
|
||||||
|
for (int y = chunkHeight - 1; y >= upperSurfaceY; y--) {
|
||||||
|
if (y == chunkHeight - 1 && bedrockEnabled) {
|
||||||
|
h.setRaw(xf, y, zf, bedrock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!upperColumn.isSolid(y)) {
|
||||||
|
h.setRaw(xf, y, zf, air);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int faceY = upperColumn.faceY(y);
|
||||||
|
int sourceSurfaceY = upperColumn.height() - 1 - faceY;
|
||||||
|
if (sourceSurfaceY != paletteSourceY) {
|
||||||
|
paletteSourceY = sourceSurfaceY;
|
||||||
|
upperBlocks = null;
|
||||||
|
}
|
||||||
|
if (y == faceY && upperMappedSurface != null) {
|
||||||
|
h.setRaw(xf, y, zf, upperMappedSurface);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int depthFromFace = y - faceY;
|
||||||
|
if (upperBlocks == null && upperBiome != null) {
|
||||||
|
upperBlocks = upperBiome.generateLayersWithSlope(upperContext.getDimension(),
|
||||||
|
realX, realZ, rng, sourceSurfaceY, sourceSurfaceY,
|
||||||
|
upperContext.getData(), upperContext.getSurfaceSlopeStream(sourceSurfaceY));
|
||||||
|
}
|
||||||
|
if (upperBlocks != null && upperBlocks.hasIndex(depthFromFace)) {
|
||||||
|
h.setRaw(xf, y, zf, upperBlocks.get(depthFromFace));
|
||||||
|
} else {
|
||||||
|
h.setRaw(xf, y, zf, upperRock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import art.arcane.iris.generation.cave.IrisCaveProfile;
|
|||||||
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
||||||
import art.arcane.iris.generation.decoration.IrisDepositVariant;
|
import art.arcane.iris.generation.decoration.IrisDepositVariant;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGeneratorBounds;
|
import art.arcane.iris.generation.decoration.IrisOreBands;
|
||||||
import art.arcane.iris.generation.decoration.tree.IrisTreeSettings;
|
import art.arcane.iris.generation.decoration.tree.IrisTreeSettings;
|
||||||
import art.arcane.iris.generation.hydrology.IrisDeepFluidConfig;
|
import art.arcane.iris.generation.hydrology.IrisDeepFluidConfig;
|
||||||
import art.arcane.iris.generation.hydrology.IrisHydrology;
|
import art.arcane.iris.generation.hydrology.IrisHydrology;
|
||||||
@@ -137,8 +137,8 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
private final transient AtomicCache<Map<String, IrisDimensionCarvingEntry>> carvingEntryIndex = new AtomicCache<>();
|
private final transient AtomicCache<Map<String, IrisDimensionCarvingEntry>> carvingEntryIndex = new AtomicCache<>();
|
||||||
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
|
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreBands> surfaceOreBandsCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreBands> undergroundOreBandsCache = new AtomicCache<>();
|
||||||
@MinNumber(2)
|
@MinNumber(2)
|
||||||
@Required
|
@Required
|
||||||
@Description("The human readable name of this dimension")
|
@Description("The human readable name of this dimension")
|
||||||
@@ -484,24 +484,16 @@ public class IrisDimension extends IrisRegistrant {
|
|||||||
this.ores = ores == null ? new KList<>() : ores;
|
this.ores = ores == null ? new KList<>() : ores;
|
||||||
surfaceOreCache.reset();
|
surfaceOreCache.reset();
|
||||||
undergroundOreCache.reset();
|
undergroundOreCache.reset();
|
||||||
surfaceOreBoundsCache.reset();
|
surfaceOreBandsCache.reset();
|
||||||
undergroundOreBoundsCache.reset();
|
undergroundOreBandsCache.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KList<IrisOreGenerator> getSurfaceOreGenerators() {
|
public IrisOreBands getSurfaceOreBands() {
|
||||||
return getOres(true);
|
return surfaceOreBandsCache.aquire(() -> IrisOreBands.of(getSurfaceOres()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public KList<IrisOreGenerator> getUndergroundOreGenerators() {
|
public IrisOreBands getUndergroundOreBands() {
|
||||||
return getOres(false);
|
return undergroundOreBandsCache.aquire(() -> IrisOreBands.of(getUndergroundOres()));
|
||||||
}
|
|
||||||
|
|
||||||
public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() {
|
|
||||||
return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() {
|
|
||||||
return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KList<IrisOreGenerator> getSurfaceOres() {
|
private KList<IrisOreGenerator> getSurfaceOres() {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import art.arcane.iris.generation.cave.IrisCaveProfile;
|
|||||||
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
||||||
import art.arcane.iris.generation.decoration.IrisDepositVariant;
|
import art.arcane.iris.generation.decoration.IrisDepositVariant;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
||||||
import art.arcane.iris.generation.decoration.IrisOreGeneratorBounds;
|
import art.arcane.iris.generation.decoration.IrisOreBands;
|
||||||
import art.arcane.iris.generation.decoration.IrisProceduralObjects;
|
import art.arcane.iris.generation.decoration.IrisProceduralObjects;
|
||||||
import art.arcane.iris.generation.hydrology.IrisRiverPolicy;
|
import art.arcane.iris.generation.hydrology.IrisRiverPolicy;
|
||||||
import art.arcane.iris.pack.validation.CompatPools;
|
import art.arcane.iris.pack.validation.CompatPools;
|
||||||
@@ -88,8 +88,8 @@ public class IrisRegion extends IrisRegistrant implements Rarity {
|
|||||||
private final transient AtomicCache<Color> cacheColor = new AtomicCache<>();
|
private final transient AtomicCache<Color> cacheColor = new AtomicCache<>();
|
||||||
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
|
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreBands> surfaceOreBandsCache = new AtomicCache<>();
|
||||||
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
|
private final transient AtomicCache<IrisOreBands> undergroundOreBandsCache = new AtomicCache<>();
|
||||||
@MinNumber(2)
|
@MinNumber(2)
|
||||||
@Required
|
@Required
|
||||||
@Description("The name of the region")
|
@Description("The name of the region")
|
||||||
@@ -217,24 +217,16 @@ public class IrisRegion extends IrisRegistrant implements Rarity {
|
|||||||
this.ores = ores == null ? new KList<>() : ores;
|
this.ores = ores == null ? new KList<>() : ores;
|
||||||
surfaceOreCache.reset();
|
surfaceOreCache.reset();
|
||||||
undergroundOreCache.reset();
|
undergroundOreCache.reset();
|
||||||
surfaceOreBoundsCache.reset();
|
surfaceOreBandsCache.reset();
|
||||||
undergroundOreBoundsCache.reset();
|
undergroundOreBandsCache.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KList<IrisOreGenerator> getSurfaceOreGenerators() {
|
public IrisOreBands getSurfaceOreBands() {
|
||||||
return getOres(true);
|
return surfaceOreBandsCache.aquire(() -> IrisOreBands.of(getSurfaceOres()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public KList<IrisOreGenerator> getUndergroundOreGenerators() {
|
public IrisOreBands getUndergroundOreBands() {
|
||||||
return getOres(false);
|
return undergroundOreBandsCache.aquire(() -> IrisOreBands.of(getUndergroundOres()));
|
||||||
}
|
|
||||||
|
|
||||||
public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() {
|
|
||||||
return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() {
|
|
||||||
return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KList<IrisOreGenerator> getSurfaceOres() {
|
private KList<IrisOreGenerator> getSurfaceOres() {
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package art.arcane.iris.generation.decoration;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.block.IrisBlockData;
|
||||||
|
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||||
|
import art.arcane.iris.generation.noise.NoiseStyle;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
|
||||||
|
import art.arcane.iris.pack.value.IrisRange;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import art.arcane.volmlib.util.noise.CNG;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
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.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class IrisOreBandsTest {
|
||||||
|
private static final RNG RNG_SOURCE = new RNG(77L);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bandsResolveTheSameOreAsScanningEveryGeneratorInOrder() {
|
||||||
|
Random random = new Random(1337L);
|
||||||
|
for (int trial = 0; trial < 200; trial++) {
|
||||||
|
KList<IrisOreGenerator> ores = new KList<>();
|
||||||
|
int count = 1 + random.nextInt(12);
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
double min = random.nextInt(90) - 10 + (random.nextBoolean() ? 0D : random.nextDouble());
|
||||||
|
double max = min + random.nextInt(40) + (random.nextBoolean() ? 0D : random.nextDouble());
|
||||||
|
ores.add(generator(min, max, 0.2D + random.nextDouble() * 0.8D, 31L * trial + index));
|
||||||
|
}
|
||||||
|
IrisOreBands bands = IrisOreBands.of(ores);
|
||||||
|
double lowest = Double.POSITIVE_INFINITY;
|
||||||
|
double highest = Double.NEGATIVE_INFINITY;
|
||||||
|
for (IrisOreGenerator ore : ores) {
|
||||||
|
lowest = Math.min(lowest, ore.getRange().getMin());
|
||||||
|
highest = Math.max(highest, ore.getRange().getMax());
|
||||||
|
}
|
||||||
|
for (int y = -20; y <= 140; y++) {
|
||||||
|
assertEquals("trial " + trial + " y " + y, y >= lowest && y <= highest, bands.contains(y));
|
||||||
|
if (!bands.contains(y)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int x = -3; x <= 3; x++) {
|
||||||
|
assertSame("trial " + trial + " y " + y + " x " + x,
|
||||||
|
scan(ores, x, y, 5 - x), bands.generate(x, y, 5 - x, RNG_SOURCE, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyAndUnboundedListsHaveNoBands() {
|
||||||
|
assertFalse(IrisOreBands.of(null).hasOres());
|
||||||
|
assertFalse(IrisOreBands.of(new KList<>()).contains(0));
|
||||||
|
KList<IrisOreGenerator> inverted = new KList<>();
|
||||||
|
inverted.add(generator(40D, 10D, 1D, 3L));
|
||||||
|
assertFalse(IrisOreBands.of(inverted).contains(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constantChanceMatchesSamplingTheFlatField() {
|
||||||
|
IrisOreGenerator flat = new IrisOreGenerator();
|
||||||
|
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
|
||||||
|
NativeBlockState ore = mock(NativeBlockState.class);
|
||||||
|
when(palette.getPalette()).thenReturn(new KList<>(mock(IrisBlockData.class)));
|
||||||
|
when(palette.get(any(), anyDouble(), anyDouble(), anyDouble(), isNull())).thenReturn(ore);
|
||||||
|
IrisGeneratorStyle style = new IrisGeneratorStyle(NoiseStyle.FLAT).setZoom(2.5D).setExponent(1.3D)
|
||||||
|
.setFracture(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).setMultiplier(9D));
|
||||||
|
flat.setPalette(palette).setChanceStyle(style).setRange(new IrisRange(0, 100));
|
||||||
|
CNG reference = style.create(RNG_SOURCE, null);
|
||||||
|
for (double threshold : new double[]{0.1D, 0.5D, 0.99D, 1D}) {
|
||||||
|
flat.setThreshold(threshold);
|
||||||
|
for (int index = 0; index < 100; index++) {
|
||||||
|
int x = index * 37 - 900;
|
||||||
|
int y = index % 101;
|
||||||
|
int z = 400 - index * 11;
|
||||||
|
NativeBlockState expected = reference.noise(x, y, z) > threshold ? null : ore;
|
||||||
|
assertSame(expected, flat.generate(x, y, z, RNG_SOURCE, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NativeBlockState scan(KList<IrisOreGenerator> ores, int x, int y, int z) {
|
||||||
|
for (IrisOreGenerator ore : ores) {
|
||||||
|
NativeBlockState state = ore.generate(x, y, z, RNG_SOURCE, null);
|
||||||
|
if (state != null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisOreGenerator generator(double min, double max, double threshold, long seed) {
|
||||||
|
IrisOreGenerator generator = new IrisOreGenerator();
|
||||||
|
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
|
||||||
|
NativeBlockState state = mock(NativeBlockState.class);
|
||||||
|
when(palette.getPalette()).thenReturn(new KList<>(mock(IrisBlockData.class)));
|
||||||
|
when(palette.get(any(), anyDouble(), anyDouble(), anyDouble(), isNull())).thenReturn(state);
|
||||||
|
CNG chance = mock(CNG.class);
|
||||||
|
when(chance.noise(anyDouble(), anyDouble(), anyDouble())).thenAnswer(invocation -> {
|
||||||
|
long hash = seed;
|
||||||
|
hash = hash * 31 + Double.doubleToLongBits(invocation.getArgument(0));
|
||||||
|
hash = hash * 31 + Double.doubleToLongBits(invocation.getArgument(1));
|
||||||
|
hash = hash * 31 + Double.doubleToLongBits(invocation.getArgument(2));
|
||||||
|
return new Random(hash).nextDouble();
|
||||||
|
});
|
||||||
|
IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
|
||||||
|
when(style.create(any(), isNull())).thenReturn(chance);
|
||||||
|
return generator.setPalette(palette).setChanceStyle(style).setThreshold(threshold)
|
||||||
|
.setRange(new IrisRange(min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void generatorsWithDisjointRangesDoNotShadowEachOther() {
|
||||||
|
KList<IrisOreGenerator> ores = new KList<>();
|
||||||
|
ores.add(generator(0D, 0D, 1D, 1L));
|
||||||
|
ores.add(generator(1D, 57D, 1D, 2L));
|
||||||
|
ores.add(generator(58D, 58D, 1D, 3L));
|
||||||
|
IrisOreBands bands = IrisOreBands.of(ores);
|
||||||
|
assertSame(scan(ores, 0, 0, 0), bands.generate(0, 0, 0, RNG_SOURCE, null));
|
||||||
|
assertSame(scan(ores, 0, 30, 0), bands.generate(0, 30, 0, RNG_SOURCE, null));
|
||||||
|
assertSame(scan(ores, 0, 58, 0), bands.generate(0, 58, 0, RNG_SOURCE, null));
|
||||||
|
assertNull(bands.contains(59) ? bands.generate(0, 59, 0, RNG_SOURCE, null) : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ChunkBlockUpdateListenerTest {
|
||||||
|
@Test
|
||||||
|
public void forwardsExactlyTheWritesWithBlockUpdateEffects() {
|
||||||
|
NativeBlockState stone = state(false, false);
|
||||||
|
NativeBlockState deepslate = state(false, false);
|
||||||
|
NativeBlockState water = state(true, false);
|
||||||
|
NativeBlockState custom = state(false, true);
|
||||||
|
NativeBlockState[] palette = {stone, deepslate, water, custom, null};
|
||||||
|
Random random = new Random(42L);
|
||||||
|
List<String> expected = new ArrayList<>();
|
||||||
|
List<String> actual = new ArrayList<>();
|
||||||
|
BlockUpdater reference = effects(expected);
|
||||||
|
ChunkBlockUpdateListener listener = new ChunkBlockUpdateListener(effects(actual), 160, -48);
|
||||||
|
|
||||||
|
for (int write = 0; write < 20_000; write++) {
|
||||||
|
NativeBlockState state = palette[random.nextInt(8) < 5 ? random.nextInt(2) : random.nextInt(palette.length)];
|
||||||
|
int x = random.nextInt(16);
|
||||||
|
int y = random.nextInt(384);
|
||||||
|
int z = random.nextInt(16);
|
||||||
|
reference.catchBlockUpdates(160 + x, y, -48 + z, state);
|
||||||
|
listener.onWrite(x, y, z, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockUpdater effects(List<String> log) {
|
||||||
|
return (x, y, z, data) -> {
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.isUpdatable()) {
|
||||||
|
log.add("update " + x + "," + y + "," + z);
|
||||||
|
}
|
||||||
|
if (data.isCustom()) {
|
||||||
|
log.add("custom " + (x >> 4) + "," + (z >> 4));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NativeBlockState state(boolean updatable, boolean custom) {
|
||||||
|
NativeBlockState state = mock(NativeBlockState.class);
|
||||||
|
when(state.isUpdatable()).thenReturn(updatable);
|
||||||
|
when(state.isCustom()).thenReturn(custom);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -291,7 +291,7 @@ public class DimensionTerrain3DCompositionTest {
|
|||||||
when(context.getRock()).thenReturn(rocks);
|
when(context.getRock()).thenReturn(rocks);
|
||||||
when(context.getRoundedHeight(0, 0)).thenReturn(8);
|
when(context.getRoundedHeight(0, 0)).thenReturn(8);
|
||||||
Hunk<NativeBlockState> blocks = Hunk.newArrayHunk(1, 64, 1);
|
Hunk<NativeBlockState> blocks = Hunk.newArrayHunk(1, 64, 1);
|
||||||
new IrisTerrainNormalActuator(engine).terrainSliver(0, 0, 0, blocks, context);
|
new IrisTerrainNormalActuator(engine).paint(0, 0, blocks, context);
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+425
@@ -0,0 +1,425 @@
|
|||||||
|
package art.arcane.iris.generation.stage;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.block.B;
|
||||||
|
import art.arcane.iris.generation.block.IrisBlockData;
|
||||||
|
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
||||||
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
|
import art.arcane.iris.generation.context.ChunkedDataCache;
|
||||||
|
import art.arcane.iris.generation.decoration.IrisOreBands;
|
||||||
|
import art.arcane.iris.generation.decoration.IrisOreGenerator;
|
||||||
|
import art.arcane.iris.generation.hydrology.HydrologyColumnLayer;
|
||||||
|
import art.arcane.iris.generation.hydrology.HydrologyColumnSample;
|
||||||
|
import art.arcane.iris.generation.hydrology.HydrologyFeatureRef;
|
||||||
|
import art.arcane.iris.generation.hydrology.HydrologyFeatureType;
|
||||||
|
import art.arcane.iris.generation.hydrology.IrisHydrology;
|
||||||
|
import art.arcane.iris.generation.hydrology.IrisRiverMaterialConfig;
|
||||||
|
import art.arcane.iris.generation.hydrology.IrisSurfaceRiverBedConfig;
|
||||||
|
import art.arcane.iris.generation.image.IrisImageMapRuntime;
|
||||||
|
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||||
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
|
import art.arcane.iris.generation.runtime.SeedManager;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||||
|
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
||||||
|
import art.arcane.iris.generation.terrain.Terrain3DColumnFixtures;
|
||||||
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.pack.value.IrisRange;
|
||||||
|
import art.arcane.iris.spi.IrisPlatform;
|
||||||
|
import art.arcane.iris.spi.PlatformRegistries;
|
||||||
|
import art.arcane.iris.testsupport.PlatformBinding;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import art.arcane.volmlib.util.noise.CNG;
|
||||||
|
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||||
|
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints randomized columns (Terrain3D spans with cave ceilings, biome layers, river materials, a
|
||||||
|
* mapped surface, bedrock, fluid, and surface and underground ores at every level) and compares
|
||||||
|
* each block with the per-block algorithm the painter replaced.
|
||||||
|
*/
|
||||||
|
public class IrisTerrainNormalActuatorColumnParityTest {
|
||||||
|
private static final int HEIGHT = 96;
|
||||||
|
private static final int X = 21;
|
||||||
|
private static final int Z = -7;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final PlatformBinding platform = PlatformBinding.of(IrisTerrainNormalActuatorColumnParityTest::distinctBlocks);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void paintedColumnsMatchThePerBlockAlgorithm() {
|
||||||
|
Random random = new Random(20260928L);
|
||||||
|
for (int trial = 0; trial < 600; trial++) {
|
||||||
|
Fixture fixture = new Fixture(random, trial);
|
||||||
|
Hunk<NativeBlockState> painted = Hunk.newArrayHunk(1, HEIGHT, 1);
|
||||||
|
new IrisTerrainNormalActuator(fixture.engine).paint(X, Z, painted, fixture.context);
|
||||||
|
Hunk<NativeBlockState> expected = Hunk.newArrayHunk(1, HEIGHT, 1);
|
||||||
|
fixture.reference(expected);
|
||||||
|
for (int y = 0; y < HEIGHT; y++) {
|
||||||
|
assertEquals("trial " + trial + " y " + y + " " + fixture,
|
||||||
|
key(expected.getRaw(0, y, 0)), key(painted.getRaw(0, y, 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String key(NativeBlockState state) {
|
||||||
|
return state == null ? "null" : state.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisPlatform distinctBlocks() {
|
||||||
|
Map<String, NativeBlockState> blocks = new HashMap<>();
|
||||||
|
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||||
|
when(registries.block(anyString())).thenAnswer(invocation ->
|
||||||
|
blocks.computeIfAbsent(invocation.getArgument(0), Fixture::state));
|
||||||
|
IrisPlatform platform = mock(IrisPlatform.class);
|
||||||
|
when(platform.registries()).thenReturn(registries);
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Fixture {
|
||||||
|
private final Engine engine = mock(Engine.class);
|
||||||
|
private final ChunkContext context = mock(ChunkContext.class);
|
||||||
|
private final IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
private final IrisData data = mock(IrisData.class);
|
||||||
|
private final IrisBiome biome = mock(IrisBiome.class);
|
||||||
|
private final IrisRegion region = new IrisRegion();
|
||||||
|
private final IrisDimension dimension;
|
||||||
|
private final RNG rng = new RNG(0L);
|
||||||
|
private final NativeBlockState rock = state("rock");
|
||||||
|
private final NativeBlockState fluid = state("water");
|
||||||
|
private final NativeBlockState mapped;
|
||||||
|
private final Terrain3DColumn terrainColumn;
|
||||||
|
private final HydrologyColumnSample hydrology;
|
||||||
|
private final int he;
|
||||||
|
private final int surfaceFluid;
|
||||||
|
private final KList<IrisOreGenerator> biomeSurface = new KList<>();
|
||||||
|
private final KList<IrisOreGenerator> biomeUnderground = new KList<>();
|
||||||
|
private final KList<IrisOreGenerator> regionSurface = new KList<>();
|
||||||
|
private final KList<IrisOreGenerator> regionUnderground = new KList<>();
|
||||||
|
private final KList<IrisOreGenerator> dimensionSurface = new KList<>();
|
||||||
|
private final KList<IrisOreGenerator> dimensionUnderground = new KList<>();
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
private Fixture(Random random, int trial) {
|
||||||
|
he = 3 + random.nextInt(HEIGHT);
|
||||||
|
surfaceFluid = random.nextInt(HEIGHT + 4);
|
||||||
|
boolean bedrock = random.nextBoolean();
|
||||||
|
mapped = random.nextInt(4) == 0 ? state("mapped") : null;
|
||||||
|
int layerCount = random.nextInt(6);
|
||||||
|
int seaCount = random.nextInt(3);
|
||||||
|
int ceilingCount = random.nextInt(3);
|
||||||
|
terrainColumn = random.nextBoolean() ? null : spans(random, Math.min(he, HEIGHT - 1));
|
||||||
|
IrisRiverMaterialConfig material = null;
|
||||||
|
if (random.nextInt(3) == 0) {
|
||||||
|
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
|
||||||
|
NativeBlockState riverBlock = state("river");
|
||||||
|
when(palette.get(any(RNG.class), anyDouble(), anyDouble(), anyDouble(), eq(data))).thenReturn(riverBlock);
|
||||||
|
material = new IrisRiverMaterialConfig().setEnabled(true).setDepth(random.nextInt(8)).setPalette(palette);
|
||||||
|
}
|
||||||
|
hydrology = material == null ? null : new HydrologyColumnSample(X, Z, he + 5, 0, false, "parent",
|
||||||
|
List.of(bankLayer(random.nextBoolean())));
|
||||||
|
IrisHydrology rivers = new IrisHydrology();
|
||||||
|
if (material != null) {
|
||||||
|
rivers.getRivers().getSurface().getBanks().setShoreMaterial(material).setBankMaterial(material);
|
||||||
|
}
|
||||||
|
for (int level = 0; level < 6; level++) {
|
||||||
|
KList<IrisOreGenerator> target = List.of(biomeSurface, biomeUnderground, regionSurface,
|
||||||
|
regionUnderground, dimensionSurface, dimensionUnderground).get(level);
|
||||||
|
int count = random.nextInt(3) == 0 ? 0 : random.nextInt(4);
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
double min = random.nextInt(HEIGHT);
|
||||||
|
target.add(ore(min, min + random.nextInt(30), 0.3D + random.nextDouble() * 0.7D,
|
||||||
|
level % 2 == 0, 97L * trial + 13L * level + index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KList<IrisOreGenerator> regionOres = new KList<>(regionSurface);
|
||||||
|
regionOres.addAll(regionUnderground);
|
||||||
|
region.setOres(regionOres);
|
||||||
|
KList<IrisOreGenerator> dimensionOres = new KList<>(dimensionSurface);
|
||||||
|
dimensionOres.addAll(dimensionUnderground);
|
||||||
|
dimension = new IrisDimension().setHydrology(rivers).setBedrock(bedrock).setHideOresForHiddenOre(false);
|
||||||
|
dimension.setOres(dimensionOres);
|
||||||
|
|
||||||
|
when(engine.getDimension()).thenReturn(dimension);
|
||||||
|
when(engine.getComplex()).thenReturn(complex);
|
||||||
|
when(engine.getData()).thenReturn(data);
|
||||||
|
when(engine.getSeedManager()).thenReturn(mock(SeedManager.class));
|
||||||
|
IrisImageMapRuntime imageMap = mock(IrisImageMapRuntime.class);
|
||||||
|
when(imageMap.sampleSurfaceBlock(anyDouble(), anyDouble())).thenReturn(mapped);
|
||||||
|
when(complex.getImageMapRuntime()).thenReturn(imageMap);
|
||||||
|
when(complex.getRiverWaterSurfaceStream()).thenReturn(ProceduralStream.ofDouble((x, z) -> (double) surfaceFluid));
|
||||||
|
when(complex.sampleHydrologyColumn(X, Z)).thenReturn(hydrology);
|
||||||
|
when(complex.terrainColumn(eq(X), eq(Z), hydrology == null ? isNull() : any(HydrologyColumnSample.class)))
|
||||||
|
.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.getBiome()).thenReturn(new ChunkedDataCache<>(
|
||||||
|
ProceduralStream.of((x, z) -> biome, Interpolated.of(value -> 0D, value -> biome)), X, Z, false));
|
||||||
|
when(context.getRegion()).thenReturn(new ChunkedDataCache<>(
|
||||||
|
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> region)), X, Z, false));
|
||||||
|
when(context.getRock()).thenReturn(new ChunkedDataCache<>(
|
||||||
|
ProceduralStream.of((x, z) -> rock, Interpolated.of(value -> 0D, value -> rock)), X, Z, false));
|
||||||
|
when(biome.generateLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
||||||
|
anyInt(), anyInt(), eq(data), eq(complex))).thenAnswer(invocation -> layers("layer", layerCount));
|
||||||
|
when(biome.generateSeaLayers(anyDouble(), anyDouble(), any(RNG.class), anyInt(), eq(data)))
|
||||||
|
.thenAnswer(invocation -> layers("sea", seaCount));
|
||||||
|
when(biome.generateCeilingLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
||||||
|
anyInt(), anyInt(), eq(data), eq(complex))).thenAnswer(invocation -> layers("ceiling", ceilingCount));
|
||||||
|
when(biome.getSurfaceOreBands()).thenReturn(IrisOreBands.of(biomeSurface));
|
||||||
|
when(biome.getUndergroundOreBands()).thenReturn(IrisOreBands.of(biomeUnderground));
|
||||||
|
description = "he=" + he + " fluid=" + surfaceFluid + " bedrock=" + bedrock + " mapped=" + (mapped != null)
|
||||||
|
+ " layers=" + layerCount + " column=" + terrainColumn + " material=" + (material == null ? -1 : material.getDepth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reference(Hunk<NativeBlockState> h) {
|
||||||
|
int chunkHeight = h.getHeight();
|
||||||
|
int xf = 0;
|
||||||
|
int zf = 0;
|
||||||
|
int realX = X;
|
||||||
|
int realZ = Z;
|
||||||
|
NativeBlockState bedrockState = B.getState("BEDROCK");
|
||||||
|
NativeBlockState airState = B.getState("AIR");
|
||||||
|
IrisSurfaceRiverBedConfig riverBed = dimension.getHydrology().getRivers().getSurface().getBed();
|
||||||
|
int he = Math.min(chunkHeight, this.he);
|
||||||
|
int surfaceFluidHeight = Math.min(chunkHeight, this.surfaceFluid);
|
||||||
|
int hf = Math.max(surfaceFluidHeight, he);
|
||||||
|
if (hf < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int topY = Math.min(hf, chunkHeight - 1);
|
||||||
|
HydrologyColumnLayer hydrologyFluid = hydrology == null ? null : hydrology.primarySurfaceFluidLayerOrNull();
|
||||||
|
HydrologyColumnLayer hydrologyTerrain = hydrology == null ? null : hydrology.primarySurfaceLayerOrNull();
|
||||||
|
int terrainSpan = terrainColumn == null ? -1 : terrainColumn.spanCount() - 1;
|
||||||
|
int layerSurfaceY = he;
|
||||||
|
int layerCeilingY = 0;
|
||||||
|
boolean exposeRiverStrata = dimension.getHydrology().getRivers().getSurface().getBanks().isExposeCutStrata()
|
||||||
|
&& hydrologyTerrain != null && hydrologyTerrain.terrainOwned() && !hydrologyTerrain.channel();
|
||||||
|
Terrain3DColumn naturalColumn = exposeRiverStrata ? terrainColumn : null;
|
||||||
|
int cut = 0;
|
||||||
|
boolean riverOwned = !riverBed.isAllowGravityBlocks()
|
||||||
|
&& hydrologyTerrain != null && hydrologyTerrain.terrainOwned();
|
||||||
|
IrisRiverMaterialConfig roleMaterial = IrisTerrainNormalActuator.hydrologyRoleMaterial(hydrologyTerrain,
|
||||||
|
riverBed.getMaterial(),
|
||||||
|
dimension.getHydrology().getRivers().getSurface().getBanks().getShoreMaterial(),
|
||||||
|
dimension.getHydrology().getRivers().getSurface().getBanks().getBankMaterial());
|
||||||
|
OldBounds biomeSurfaceBounds = new OldBounds(biomeSurface);
|
||||||
|
OldBounds regionSurfaceBounds = new OldBounds(regionSurface);
|
||||||
|
OldBounds dimensionSurfaceBounds = new OldBounds(dimensionSurface);
|
||||||
|
OldBounds biomeUndergroundBounds = new OldBounds(biomeUnderground);
|
||||||
|
OldBounds regionUndergroundBounds = new OldBounds(regionUnderground);
|
||||||
|
OldBounds dimensionUndergroundBounds = new OldBounds(dimensionUnderground);
|
||||||
|
boolean hasSurfaceOres = biomeSurfaceBounds.hasOres || regionSurfaceBounds.hasOres || dimensionSurfaceBounds.hasOres;
|
||||||
|
boolean hasUndergroundOres = biomeUndergroundBounds.hasOres || regionUndergroundBounds.hasOres || dimensionUndergroundBounds.hasOres;
|
||||||
|
KList<NativeBlockState> blocks = null;
|
||||||
|
KList<NativeBlockState> ceilingBlocks = null;
|
||||||
|
KList<NativeBlockState> fblocks = null;
|
||||||
|
|
||||||
|
for (int i = topY; i >= 0; i--) {
|
||||||
|
if (i == 0 && dimension.isBedrock()) {
|
||||||
|
h.setRaw(xf, i, zf, bedrockState);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (terrainColumn != null && i <= he) {
|
||||||
|
while (terrainSpan >= 0 && i < terrainColumn.ceiling(terrainSpan)) {
|
||||||
|
terrainSpan--;
|
||||||
|
blocks = null;
|
||||||
|
ceilingBlocks = null;
|
||||||
|
}
|
||||||
|
if (terrainSpan < 0 || i > terrainColumn.floor(terrainSpan)) {
|
||||||
|
h.setRaw(xf, i, zf, airState);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
layerSurfaceY = terrainColumn.floor(terrainSpan);
|
||||||
|
layerCeilingY = terrainColumn.ceiling(terrainSpan);
|
||||||
|
}
|
||||||
|
NativeBlockState ore = null;
|
||||||
|
if (hasSurfaceOres) {
|
||||||
|
if (biomeSurfaceBounds.contains(i)) {
|
||||||
|
ore = scan(biomeSurface, realX, i, realZ);
|
||||||
|
}
|
||||||
|
if (ore == null && regionSurfaceBounds.contains(i)) {
|
||||||
|
ore = scan(regionSurface, realX, i, realZ);
|
||||||
|
}
|
||||||
|
if (ore == null && dimensionSurfaceBounds.contains(i)) {
|
||||||
|
ore = scan(dimensionSurface, realX, i, realZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ore != null) {
|
||||||
|
h.setRaw(xf, i, zf, ore);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i > he && i <= hf) {
|
||||||
|
int fdepth = hf - i;
|
||||||
|
if (hydrologyFluid == null && fblocks == null) {
|
||||||
|
fblocks = biome.generateSeaLayers(realX, realZ, rng, hf - he, data);
|
||||||
|
}
|
||||||
|
h.setRaw(xf, i, zf, HydrologyFluidLayerSelector.select(fblocks, fdepth, fluid, hydrologyFluid != null));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i <= he) {
|
||||||
|
int depth = layerSurfaceY - i;
|
||||||
|
if (depth == 0 && mapped != null) {
|
||||||
|
h.setRaw(xf, i, zf, mapped);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (blocks == null) {
|
||||||
|
if (exposeRiverStrata) {
|
||||||
|
int naturalSurfaceY = naturalColumn == null ? hydrology.naturalHeight() : naturalColumn.surfaceY(layerSurfaceY);
|
||||||
|
cut = Math.max(0, naturalSurfaceY - layerSurfaceY);
|
||||||
|
}
|
||||||
|
blocks = biome.generateLayers(dimension, realX, realZ, rng,
|
||||||
|
layerSurfaceY + cut, layerSurfaceY + cut, data, complex);
|
||||||
|
}
|
||||||
|
if (layerCeilingY > 0 && depth >= 2 && i - layerCeilingY < 2) {
|
||||||
|
if (ceilingBlocks == null) {
|
||||||
|
ceilingBlocks = biome.generateCeilingLayers(dimension, realX, realZ, rng, 2, layerCeilingY, data, complex);
|
||||||
|
}
|
||||||
|
int ceilingDepth = i - layerCeilingY;
|
||||||
|
h.setRaw(xf, i, zf, ceilingBlocks.hasIndex(ceilingDepth) ? ceilingBlocks.get(ceilingDepth) : rock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int strataIndex = IrisTerrainNormalActuator.strataIndex(depth, cut, blocks.size());
|
||||||
|
NativeBlockState layerBlock = IrisTerrainNormalActuator.paintHydrologyMaterial(
|
||||||
|
blocks.hasIndex(strataIndex) ? blocks.get(strataIndex) : null,
|
||||||
|
roleMaterial, depth, rng, realX, i, realZ, data);
|
||||||
|
if (layerBlock != null) {
|
||||||
|
if (riverOwned && depth <= riverBed.getPadding() && IrisProceduralBlocks.isGravityAffected(layerBlock)) {
|
||||||
|
layerBlock = riverBed.getPaddingPalette().get(rng, realX, i, realZ, data);
|
||||||
|
}
|
||||||
|
h.setRaw(xf, i, zf, layerBlock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (hasUndergroundOres) {
|
||||||
|
if (biomeUndergroundBounds.contains(i)) {
|
||||||
|
ore = scan(biomeUnderground, realX, i, realZ);
|
||||||
|
}
|
||||||
|
if (ore == null && regionUndergroundBounds.contains(i)) {
|
||||||
|
ore = scan(regionUnderground, realX, i, realZ);
|
||||||
|
}
|
||||||
|
if (ore == null && dimensionUndergroundBounds.contains(i)) {
|
||||||
|
ore = scan(dimensionUnderground, realX, i, realZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.setRaw(xf, i, zf, ore != null ? ore : rock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NativeBlockState scan(KList<IrisOreGenerator> ores, int x, int y, int z) {
|
||||||
|
for (IrisOreGenerator ore : ores) {
|
||||||
|
NativeBlockState state = ore.generate(x, y, z, rng, data);
|
||||||
|
if (state != null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KList<NativeBlockState> layers(String name, int count) {
|
||||||
|
KList<NativeBlockState> layers = new KList<>();
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
layers.add(state(name + index));
|
||||||
|
}
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Terrain3DColumn spans(Random random, int top) {
|
||||||
|
List<Integer> boundaries = new ArrayList<>();
|
||||||
|
boundaries.add(0);
|
||||||
|
int cursor = random.nextInt(Math.max(1, top / 3) + 1);
|
||||||
|
boundaries.add(cursor);
|
||||||
|
while (cursor + 3 < top && random.nextBoolean()) {
|
||||||
|
int ceiling = cursor + 2 + random.nextInt(Math.max(1, (top - cursor) / 2));
|
||||||
|
if (ceiling >= top) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int floor = ceiling + random.nextInt(top - ceiling + 1);
|
||||||
|
boundaries.add(ceiling);
|
||||||
|
boundaries.add(floor);
|
||||||
|
cursor = floor;
|
||||||
|
}
|
||||||
|
return Terrain3DColumnFixtures.spans(top, boundaries.stream().mapToInt(Integer::intValue).toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisOreGenerator ore(double min, double max, double threshold, boolean surface, long seed) {
|
||||||
|
IrisOreGenerator generator = new IrisOreGenerator();
|
||||||
|
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
|
||||||
|
NativeBlockState state = state("ore" + seed);
|
||||||
|
when(palette.getPalette()).thenReturn(new KList<>(mock(IrisBlockData.class)));
|
||||||
|
when(palette.get(any(), anyDouble(), anyDouble(), anyDouble(), any())).thenReturn(state);
|
||||||
|
CNG chance = mock(CNG.class);
|
||||||
|
when(chance.noise(anyDouble(), anyDouble(), anyDouble())).thenAnswer(invocation -> {
|
||||||
|
long hash = seed * 31 + Double.doubleToLongBits(invocation.getArgument(1));
|
||||||
|
return new Random(hash).nextDouble();
|
||||||
|
});
|
||||||
|
IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
|
||||||
|
when(style.create(any(), any())).thenReturn(chance);
|
||||||
|
return generator.setPalette(palette).setChanceStyle(style).setThreshold(threshold)
|
||||||
|
.setRange(new IrisRange(min, max)).setGenerateSurface(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HydrologyColumnLayer bankLayer(boolean shore) {
|
||||||
|
return new HydrologyColumnLayer(
|
||||||
|
new HydrologyFeatureRef(21L, HydrologyFeatureType.RIFFLE, 22L, 23L, X, 63, Z, 1, 0, false),
|
||||||
|
60, 63, 63, false, shore, !shore, false, false, false, true, false, false,
|
||||||
|
"default", "river", "mouth", "shore", "bank", "cave");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NativeBlockState state(String key) {
|
||||||
|
NativeBlockState state = mock(NativeBlockState.class);
|
||||||
|
when(state.key()).thenReturn("minecraft:" + key);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class OldBounds {
|
||||||
|
private final boolean hasOres;
|
||||||
|
private final double min;
|
||||||
|
private final double max;
|
||||||
|
|
||||||
|
private OldBounds(KList<IrisOreGenerator> ores) {
|
||||||
|
double low = Double.POSITIVE_INFINITY;
|
||||||
|
double high = Double.NEGATIVE_INFINITY;
|
||||||
|
for (IrisOreGenerator ore : ores) {
|
||||||
|
low = Math.min(low, ore.getRange().getMin());
|
||||||
|
high = Math.max(high, ore.getRange().getMax());
|
||||||
|
}
|
||||||
|
hasOres = !ores.isEmpty();
|
||||||
|
min = low;
|
||||||
|
max = high;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(int y) {
|
||||||
|
return hasOres && y >= min && y <= max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -263,13 +263,13 @@ public class IrisTerrainNormalActuatorHydrologyTest {
|
|||||||
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);
|
||||||
|
|
||||||
new IrisTerrainNormalActuator(engine).terrainSliver(11, -4, 0, output, context);
|
new IrisTerrainNormalActuator(engine).paint(11, -4, output, context);
|
||||||
|
|
||||||
assertSame(painted, output.get(0, 60, 0));
|
assertSame(painted, output.get(0, 60, 0));
|
||||||
assertSame(painted, output.get(0, 59, 0));
|
assertSame(painted, output.get(0, 59, 0));
|
||||||
assertSame(rock, output.get(0, 58, 0));
|
assertSame(rock, output.get(0, 58, 0));
|
||||||
material.setEnabled(false);
|
material.setEnabled(false);
|
||||||
new IrisTerrainNormalActuator(engine).terrainSliver(11, -4, 0, output, context);
|
new IrisTerrainNormalActuator(engine).paint(11, -4, output, context);
|
||||||
assertSame(grass, output.get(0, 60, 0));
|
assertSame(grass, output.get(0, 60, 0));
|
||||||
|
|
||||||
Terrain3DColumn carved = mock(Terrain3DColumn.class);
|
Terrain3DColumn carved = mock(Terrain3DColumn.class);
|
||||||
@@ -285,11 +285,11 @@ public class IrisTerrainNormalActuatorHydrologyTest {
|
|||||||
when(complex.naturalTerrainColumn(11, -4)).thenReturn(natural);
|
when(complex.naturalTerrainColumn(11, -4)).thenReturn(natural);
|
||||||
when(biome.generateCeilingLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
when(biome.generateCeilingLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
|
||||||
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>());
|
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>());
|
||||||
new IrisTerrainNormalActuator(engine).terrainSliver(11, -4, 0, output, context);
|
new IrisTerrainNormalActuator(engine).paint(11, -4, output, context);
|
||||||
assertSame(grass, output.get(0, 60, 0));
|
assertSame(grass, output.get(0, 60, 0));
|
||||||
assertSame(grass, output.get(0, 40, 0));
|
assertSame(grass, output.get(0, 40, 0));
|
||||||
material.setEnabled(true);
|
material.setEnabled(true);
|
||||||
new IrisTerrainNormalActuator(engine).terrainSliver(11, -4, 0, output, context);
|
new IrisTerrainNormalActuator(engine).paint(11, -4, output, context);
|
||||||
assertSame(painted, output.get(0, 60, 0));
|
assertSame(painted, output.get(0, 60, 0));
|
||||||
assertSame(painted, output.get(0, 40, 0));
|
assertSame(painted, output.get(0, 40, 0));
|
||||||
assertSame(rock, output.get(0, 38, 0));
|
assertSame(rock, output.get(0, 38, 0));
|
||||||
|
|||||||
+1
-1
@@ -163,7 +163,7 @@ public class IrisTerrainNormalActuatorStrataCutTest {
|
|||||||
|
|
||||||
private Hunk<NativeBlockState> actuate() {
|
private Hunk<NativeBlockState> actuate() {
|
||||||
Hunk<NativeBlockState> output = Hunk.newArrayHunk(1, 64, 1);
|
Hunk<NativeBlockState> output = Hunk.newArrayHunk(1, 64, 1);
|
||||||
new IrisTerrainNormalActuator(engine).terrainSliver(11, -4, 0, output, context);
|
new IrisTerrainNormalActuator(engine).paint(11, -4, output, context);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user