From 3bd76d47a97cf68c0b2c74119d8c6203fc32a5b9 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 21 Oct 2026 19:16:57 -0400 Subject: [PATCH] Single-pass deposit clumps - Place ellipsoid deposit clumps from the footprint cells instead of rebuilding the clump object - Skip ore cave-biome lookups when no pack biome scales ore deposits - Reject terrain-limited clumps before biome work when scaling cannot apply - Compute the chunk terrain limit once per chunk - Draw the exposure discard roll only when the discard chance is positive --- .../decoration/IrisDepositGenerator.java | 104 +++++- .../generation/stage/IrisDepositModifier.java | 310 +++++++++++------- .../decoration/IrisDepositTuningTest.java | 36 ++ .../stage/IrisDepositModifierParityTest.java | 26 +- 4 files changed, 341 insertions(+), 135 deletions(-) diff --git a/core/src/main/java/art/arcane/iris/generation/decoration/IrisDepositGenerator.java b/core/src/main/java/art/arcane/iris/generation/decoration/IrisDepositGenerator.java index 681f823e8..1ab82efa5 100644 --- a/core/src/main/java/art/arcane/iris/generation/decoration/IrisDepositGenerator.java +++ b/core/src/main/java/art/arcane/iris/generation/decoration/IrisDepositGenerator.java @@ -39,6 +39,7 @@ import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KSet; import art.arcane.volmlib.util.math.BlockPosition; import art.arcane.volmlib.util.math.RNG; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -301,9 +302,9 @@ public class IrisDepositGenerator { return ClumpFootprint.EMPTY; } for (int cell = 0; cell < cells.count; cell++) { - rng.i(0, paletteSize); + cells.picks[cell] = rng.i(0, paletteSize); } - return new ClumpFootprint(cells.extentX * 2 + 1, cells.minY, false); + return new ClumpFootprint(cells.extentX * 2 + 1, cells.minY, cells, cells.stamp, getBlockData(rdata)); } /** @@ -630,8 +631,76 @@ public class IrisDepositGenerator { void add(int x, int y, int z); } - public record ClumpFootprint(int width, int minY, boolean empty) { - static final ClumpFootprint EMPTY = new ClumpFootprint(1, 0, true); + public static final class ClumpFootprint { + static final ClumpFootprint EMPTY = new ClumpFootprint(1, 0, null, 0L, null); + + private final int width; + private final int minY; + private final FootprintCells source; + private final long stamp; + private final KList palette; + + private ClumpFootprint(int width, int minY, FootprintCells source, long stamp, KList palette) { + this.width = width; + this.minY = minY; + this.source = source; + this.stamp = stamp; + this.palette = palette; + } + + public int width() { + return width; + } + + public int minY() { + return minY; + } + + public boolean empty() { + return source == null; + } + + public ClumpCells cells() { + if (source == null) { + return ClumpCells.EMPTY; + } + if (source.stamp != stamp) { + throw new IllegalStateException("Deposit footprint cells were resampled before they were read"); + } + return source.toCells(palette); + } + } + + public static final class ClumpCells { + static final ClumpCells EMPTY = new ClumpCells(new int[0], new NativeBlockState[0]); + + private final int[] positions; + private final NativeBlockState[] blocks; + + private ClumpCells(int[] positions, NativeBlockState[] blocks) { + this.positions = positions; + this.blocks = blocks; + } + + public int size() { + return positions.length; + } + + public int x(int cell) { + return (positions[cell] >> 12) - FootprintCells.OFFSET; + } + + public int y(int cell) { + return ((positions[cell] >> 6) & (FootprintCells.AXIS - 1)) - FootprintCells.OFFSET; + } + + public int z(int cell) { + return (positions[cell] & (FootprintCells.AXIS - 1)) - FootprintCells.OFFSET; + } + + public NativeBlockState block(int cell) { + return blocks[cell]; + } } private static final class FootprintCells implements CellSink { @@ -639,10 +708,14 @@ public class IrisDepositGenerator { private static final int AXIS = 64; private final long[] occupied = new long[AXIS * AXIS * AXIS / 64]; private final int[] touched = new int[AXIS * AXIS * AXIS / 64]; + private int[] order = new int[256]; + private int[] picks = new int[256]; + private long[] sortKeys = new long[256]; private int touchedCount; private int count; private int extentX; private int minY; + private long stamp; private void reset() { for (int index = 0; index < touchedCount; index++) { @@ -652,6 +725,7 @@ public class IrisDepositGenerator { count = 0; extentX = 0; minY = Integer.MAX_VALUE; + stamp++; } @Override @@ -667,9 +741,29 @@ public class IrisDepositGenerator { touched[touchedCount++] = word; } occupied[word] = current | bit; - count++; + if (count == order.length) { + order = Arrays.copyOf(order, count * 2); + picks = Arrays.copyOf(picks, count * 2); + sortKeys = new long[count * 2]; + } + order[count++] = index; extentX = Math.max(extentX, Math.abs(x)); minY = Math.min(minY, y); } + + private ClumpCells toCells(KList palette) { + for (int cell = 0; cell < count; cell++) { + sortKeys[cell] = ((long) order[cell] << 32) | picks[cell]; + } + Arrays.sort(sortKeys, 0, count); + int[] positions = new int[count]; + NativeBlockState[] blocks = new NativeBlockState[count]; + for (int cell = 0; cell < count; cell++) { + long key = sortKeys[cell]; + positions[cell] = (int) (key >>> 32); + blocks[cell] = palette.get((int) key); + } + return new ClumpCells(positions, blocks); + } } } diff --git a/core/src/main/java/art/arcane/iris/generation/stage/IrisDepositModifier.java b/core/src/main/java/art/arcane/iris/generation/stage/IrisDepositModifier.java index c22dc82cf..35b2417d4 100644 --- a/core/src/main/java/art/arcane/iris/generation/stage/IrisDepositModifier.java +++ b/core/src/main/java/art/arcane/iris/generation/stage/IrisDepositModifier.java @@ -52,14 +52,14 @@ import art.arcane.iris.pack.loading.ResourceLoader; import java.util.ArrayList; import java.util.List; -import java.util.function.Function; +import java.util.function.Predicate; public class IrisDepositModifier extends EngineAssignedModifier { private static final int CLUMPS_PER_BATCH = 8; private static final int PREPARATION_BATCH_COUNT = 4; private final RNG rng; - private volatile DepositVariantScan variantScan; + private volatile PackScan packScan; public IrisDepositModifier(Engine engine) { super(engine, "Deposit"); @@ -87,6 +87,7 @@ public class IrisDepositModifier extends EngineAssignedModifier prepared[index] = prepare(plan, firstAttempt, limit, x, z, null, context), preparation, index)); + () -> prepared[index] = prepare(plan, firstAttempt, limit, x, z, null, context, highestSurface), + preparation, index)); if (pending == prepared.length) { completeBatches(burst, prepared, chunk, terrain, x, z, preparation); pending = 0; @@ -172,7 +174,7 @@ public class IrisDepositModifier extends EngineAssignedModifier clumps = new ArrayList<>(limit - first); - int terrainLimit = Integer.MIN_VALUE; for (int l = first; l < limit; l++) { - long clumpSeed = clumpSeed(plan.seed(), l); - RNG clumpRng = new RNG(clumpSeed); + RNG clumpRng = new RNG(clumpSeed(plan.seed(), l)); if (k.getPerClumpSpawnChance() < clumpRng.d()) { continue; } IrisDepositGenerator.ClumpFootprint footprint = k.sampleClumpFootprint(clumpRng, getData()); + if (footprint != null && footprint.empty() && !scalable) { + continue; + } IrisObject clump = footprint == null ? k.getClump(getEngine(), clumpRng, getData()) : null; int dim = footprint == null ? clump.getW() : footprint.width(); @@ -242,9 +250,9 @@ public class IrisDepositModifier extends EngineAssignedModifier terrainLimit) { + continue; + } + + int biomeY = Math.max(0, Math.min(engineHeight - 1, y)); IrisBiome surfaceBiome = context.getBiome().get(x, z); IrisBiome depositBiome = needsCaveBiome ? getEngine().getCaveBiome( @@ -272,43 +289,34 @@ public class IrisDepositModifier extends EngineAssignedModifier terrainLimit) { - continue; - } - } - RNG replay = new RNG(clumpSeed); - replay.d(); - clump = k.getClump(getEngine(), replay, getData()); + if (clump != null) { + clumps.add(new PreparedClump(clump, null, x, y, z, clumpRng)); + continue; } - - clumps.add(new PreparedClump(clump, x, y, z, clumpRng)); + if (footprint.empty() || footprintLimited && y + footprint.minY() > terrainLimit) { + continue; + } + clumps.add(new PreparedClump(null, footprint.cells(), x, y, z, clumpRng)); } return new PreparedDeposit(k, oreDeposit, clumps); } @@ -318,72 +326,24 @@ public class IrisDepositModifier extends EngineAssignedModifier.Cursor cursor = clump.getBlocks().cursor(); + IrisDepositGenerator.ClumpCells cells = prepared.cells(); + if (cells != null) { + for (int cell = 0; cell < cells.size(); cell++) { + placement.place(cells.x(cell) + x, cells.y(cell) + y, cells.z(cell) + z, cells.block(cell), rng); + } + continue; + } + VectorMap.Cursor cursor = prepared.object().getBlocks().cursor(); while (cursor.next()) { IrisBlockVector j = cursor.key(); - int nx = j.getBlockX() + x; - int ny = j.getBlockY() + y; - int nz = j.getBlockZ() + z; - - if (nx > 15 || nx < 0 || ny >= getEngine().getHeight() || ny < 0 || nz < 0 || nz > 15) { - continue; - } - int columnSurface = getDepositTerrainSurface(cx, cz, nx, nz, he, context); - if (!placementSurfaceAllows( - k.getPlacementScope(), ny, columnSurface, k.getSurfaceClearance())) { - continue; - } - - DimensionStackLayout stackLayout = context.getDimensionStackLayout(nx, nz); - if (stackLayout != null && stackLayout.isHostFeatureProtectedY(ny)) { - continue; - } - - NativeBlockState current = data.get(nx, ny, nz); - if (!canReplaceDepositTarget(current)) { - continue; - } - if (!k.isReplaceBedrock() && IrisProceduralBlocks.materialKey(current).equals("minecraft:bedrock")) { - continue; - } - - IrisBiome candidateSurfaceBiome = null; - boolean exteriorSurface = false; - if (oreDeposit) { - candidateSurfaceBiome = context.getBiome().get(nx, nz); - exteriorSurface = k.hasSurfaceReplaceableBlocks(candidateSurfaceBiome) - && isTerrainSurface(data, nx, ny, nz, columnSurface); - } - if (!canReplaceDepositHost(k, current, candidateSurfaceBiome, exteriorSurface)) { - continue; - } - boolean adjacentToAir = k.getDiscardChanceOnAirExposure() > 0D - && isAdjacentToAir(data, nx, ny, nz); - if (shouldDiscardExposed(k.getDiscardChanceOnAirExposure(), rng.d(), adjacentToAir)) { - continue; - } - - if (chunk.get(nx, ny, nz, MatterCavern.class) == null) { - NativeBlockState ore = cursor.value(); - NativeBlockState remapped = variants ? resolveDepositVariant( - cx, cz, nx, ny, nz, ore, dimension, context, carvingState) : null; - NativeBlockState finalBlock = remapped != null - ? remapped - : B.toDeepSlateOre(current, ore); - data.set(nx, ny, nz, finalBlock); - } + placement.place(j.getBlockX() + x, j.getBlockY() + y, j.getBlockZ() + z, cursor.value(), rng); } } } @@ -397,17 +357,17 @@ public class IrisDepositModifier extends EngineAssignedModifier declaresVariants(region.getDepositVariants())); + Boolean biomeVariants = anyLoaded(data.getBiomeLoader(), biome -> declaresVariants(biome.getDepositVariants())); + Boolean oreScaling = anyLoaded(data.getBiomeLoader(), IrisDepositModifier::scalesOreDeposits); + boolean variants = declaresVariants(dimension.getDepositVariants()) + || regionVariants == null || regionVariants || biomeVariants == null || biomeVariants; + return new PackScan(data, variants, oreScaling == null || oreScaling); } - private static Boolean declaresVariants(ResourceLoader loader, - Function> variants) { + private static boolean declaresVariants(List declared) { + return declared == null || !declared.isEmpty(); + } + + private static boolean scalesOreDeposits(IrisBiome biome) { + return biome.getOreDepositFrequencyMultiplier() < 1D || biome.getOreDepositSizeMultiplier() != 1D; + } + + private static Boolean anyLoaded(ResourceLoader loader, Predicate test) { String[] keys = loader == null ? null : loader.getPossibleKeys(); if (keys == null) { return null; } for (String key : keys) { T loaded = loader.load(key); - if (loaded == null) { - continue; - } - List declared = variants.apply(loaded); - if (declared == null || !declared.isEmpty()) { + if (loaded != null && test.test(loaded)) { return true; } } @@ -659,12 +632,95 @@ public class IrisDepositModifier extends EngineAssignedModifier clumps) { } - private record PreparedClump(IrisObject object, int x, int y, int z, RNG rng) { + private record PreparedClump(IrisObject object, IrisDepositGenerator.ClumpCells cells, int x, int y, int z, RNG rng) { + } + + private final class ClumpPlacement { + private final IrisDepositGenerator generator; + private final boolean ore; + private final MantleChunk chunk; + private final Hunk data; + private final int chunkX; + private final int chunkZ; + private final HeightMap heightMap; + private final ChunkContext context; + private final boolean variants; + private final IrisDimension dimension; + private final IrisDimensionCarvingResolver.State carvingState = new IrisDimensionCarvingResolver.State(); + private final IrisDepositPlacementScope scope; + private final int surfaceClearance; + private final double discardChance; + private final int height; + + private ClumpPlacement(IrisDepositGenerator generator, boolean ore, MantleChunk chunk, + Hunk data, int chunkX, int chunkZ, HeightMap heightMap, + ChunkContext context, boolean variants) { + this.generator = generator; + this.ore = ore; + this.chunk = chunk; + this.data = data; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.heightMap = heightMap; + this.context = context; + this.variants = variants; + this.dimension = getDimension(); + this.scope = generator.getPlacementScope(); + this.surfaceClearance = generator.getSurfaceClearance(); + this.discardChance = generator.getDiscardChanceOnAirExposure(); + this.height = getEngine().getHeight(); + } + + private void place(int nx, int ny, int nz, NativeBlockState block, RNG rng) { + if (nx > 15 || nx < 0 || ny >= height || ny < 0 || nz < 0 || nz > 15) { + return; + } + int columnSurface = getDepositTerrainSurface(chunkX, chunkZ, nx, nz, heightMap, context); + if (!placementSurfaceAllows(scope, ny, columnSurface, surfaceClearance)) { + return; + } + + DimensionStackLayout stackLayout = context.getDimensionStackLayout(nx, nz); + if (stackLayout != null && stackLayout.isHostFeatureProtectedY(ny)) { + return; + } + + NativeBlockState current = data.get(nx, ny, nz); + if (!canReplaceDepositTarget(current)) { + return; + } + if (!generator.isReplaceBedrock() && IrisProceduralBlocks.materialKey(current).equals("minecraft:bedrock")) { + return; + } + + IrisBiome candidateSurfaceBiome = null; + boolean exteriorSurface = false; + if (ore) { + candidateSurfaceBiome = context.getBiome().get(nx, nz); + exteriorSurface = generator.hasSurfaceReplaceableBlocks(candidateSurfaceBiome) + && isTerrainSurface(data, nx, ny, nz, columnSurface); + } + if (!canReplaceDepositHost(generator, current, candidateSurfaceBiome, exteriorSurface)) { + return; + } + if (discardChance > 0D) { + boolean adjacentToAir = isAdjacentToAir(data, nx, ny, nz); + if (shouldDiscardExposed(discardChance, rng.d(), adjacentToAir)) { + return; + } + } + + if (chunk.get(nx, ny, nz, MatterCavern.class) == null) { + NativeBlockState remapped = variants ? resolveDepositVariant( + chunkX, chunkZ, nx, ny, nz, block, dimension, context, carvingState) : null; + data.set(nx, ny, nz, remapped != null ? remapped : B.toDeepSlateOre(current, block)); + } + } } } diff --git a/core/src/test/java/art/arcane/iris/generation/decoration/IrisDepositTuningTest.java b/core/src/test/java/art/arcane/iris/generation/decoration/IrisDepositTuningTest.java index 839e18e77..a9d5fb79a 100644 --- a/core/src/test/java/art/arcane/iris/generation/decoration/IrisDepositTuningTest.java +++ b/core/src/test/java/art/arcane/iris/generation/decoration/IrisDepositTuningTest.java @@ -16,6 +16,8 @@ import org.junit.Test; import org.mockito.MockedStatic; import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -258,6 +260,40 @@ public class IrisDepositTuningTest { assertNull(generator.sampleClumpFootprint(new RNG(1L), data)); } + @Test + public void ellipsoidFootprintCellsMatchTheBuiltClumpInPlacementOrder() { + IrisData data = mock(IrisData.class); + IrisDepositGenerator generator = new IrisDepositGenerator(); + for (String key : new String[]{"minecraft:iron_ore", "minecraft:gold_ore", "minecraft:copper_ore"}) { + IrisBlockData block = mock(IrisBlockData.class); + NativeBlockState state = mock(NativeBlockState.class); + when(block.getBlockData(data)).thenReturn(state); + when(state.key()).thenReturn(key); + generator.getPalette().add(block); + } + generator.setShape(IrisDepositShape.VANILLA_ELLIPSOID); + for (int size : new int[]{0, 1, 3, 4, 9, 17, 25, 64}) { + generator.setMinSize(size).setMaxSize(size); + for (long seed = 0; seed < 300; seed++) { + IrisObject clump = generator.getClump(null, new RNG(seed), data); + List expected = new ArrayList<>(); + VectorMap.Cursor cursor = clump.getBlocks().cursor(); + while (cursor.next()) { + expected.add(cursor.key().getBlockX() + "," + cursor.key().getBlockY() + "," + + cursor.key().getBlockZ() + "=" + cursor.value().key()); + } + RNG sampled = new RNG(seed); + IrisDepositGenerator.ClumpCells cells = generator.sampleClumpFootprint(sampled, data).cells(); + List actual = new ArrayList<>(); + for (int cell = 0; cell < cells.size(); cell++) { + actual.add(cells.x(cell) + "," + cells.y(cell) + "," + cells.z(cell) + "=" + + cells.block(cell).key()); + } + assertEquals("size " + size + " seed " + seed, expected, actual); + } + } + } + @Test public void vanillaScatteredSizeIsAnUpperCandidateBound() { IrisData data = mock(IrisData.class); diff --git a/core/src/test/java/art/arcane/iris/generation/stage/IrisDepositModifierParityTest.java b/core/src/test/java/art/arcane/iris/generation/stage/IrisDepositModifierParityTest.java index e3d4ba776..68176479b 100644 --- a/core/src/test/java/art/arcane/iris/generation/stage/IrisDepositModifierParityTest.java +++ b/core/src/test/java/art/arcane/iris/generation/stage/IrisDepositModifierParityTest.java @@ -107,6 +107,18 @@ public class IrisDepositModifierParityTest { } } + @Test + public void unscaledPacksSkipOreCaveBiomeLookupsWithIdenticalOutput() { + for (long seed : new long[]{101L, -73L, 90181L}) { + AtomicInteger scannedLookups = new AtomicInteger(); + AtomicInteger unscannedLookups = new AtomicInteger(); + long[] scanned = generateConfigured(seed, false, true, true, scannedLookups, null, false); + long[] unscanned = generateConfigured(seed, false, true, false, unscannedLookups, null, false); + assertArrayEquals("seed=" + seed, unscanned, scanned); + assertTrue(scannedLookups.get() < unscannedLookups.get()); + } + } + @Test public void unmatchableSurfaceBiomeFiltersSkipClumpsWithoutChangingOutput() { long[] expected = {-681153659952315909L, 784L, 2118L, 53L, 67L, 10L}; @@ -126,9 +138,15 @@ public class IrisDepositModifierParityTest { return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, null); } - @SuppressWarnings("unchecked") private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack, AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps) { + return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, unmatchableClumps, true); + } + + @SuppressWarnings("unchecked") + private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack, + AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps, + boolean oreScaling) { Engine engine = mock(Engine.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS)); ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS)); IrisDimension dimension = new IrisDimension(); @@ -136,8 +154,10 @@ public class IrisDepositModifierParityTest { IrisBiome surface = new IrisBiome(); IrisBiome cave = new IrisBiome(); cave.setLoadKey("allowed"); - cave.setOreDepositFrequencyMultiplier(0.65D); - cave.setOreDepositSizeMultiplier(1.5D); + if (oreScaling) { + cave.setOreDepositFrequencyMultiplier(0.65D); + cave.setOreDepositSizeMultiplier(1.5D); + } IrisBiome denied = new IrisBiome(); denied.setLoadKey("denied"); IrisDepositVariant variant = new IrisDepositVariant().setMinHeight(-64).setMaxHeight(64);