Merge W2C-terrain

This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 14:44:12 -04:00
commit 491382abaa
18 files changed
+1183 -395

No files matched your search

@@ -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);
}
}
@@ -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;
}
}
@@ -291,7 +291,7 @@ public class DimensionTerrain3DCompositionTest {
when(context.getRock()).thenReturn(rocks);
when(context.getRoundedHeight(0, 0)).thenReturn(8);
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;
}
@@ -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;
}
}
}
@@ -263,13 +263,13 @@ public class IrisTerrainNormalActuatorHydrologyTest {
anyInt(), anyInt(), eq(data), eq(complex))).thenReturn(new KList<>(List.of(grass)));
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, 59, 0));
assertSame(rock, output.get(0, 58, 0));
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));
Terrain3DColumn carved = mock(Terrain3DColumn.class);
@@ -285,11 +285,11 @@ public class IrisTerrainNormalActuatorHydrologyTest {
when(complex.naturalTerrainColumn(11, -4)).thenReturn(natural);
when(biome.generateCeilingLayers(eq(dimension), anyDouble(), anyDouble(), any(RNG.class),
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, 40, 0));
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, 40, 0));
assertSame(rock, output.get(0, 38, 0));
@@ -163,7 +163,7 @@ public class IrisTerrainNormalActuatorStrataCutTest {
private Hunk<NativeBlockState> actuate() {
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;
}
}