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
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 19:16:57 -04:00
1 parent e2430500cf
commit 3bd76d47a9
4 files changed
+341 -135

No files matched your search

@@ -39,6 +39,7 @@ import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KSet; import art.arcane.volmlib.util.collection.KSet;
import art.arcane.volmlib.util.math.BlockPosition; import art.arcane.volmlib.util.math.BlockPosition;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -301,9 +302,9 @@ public class IrisDepositGenerator {
return ClumpFootprint.EMPTY; return ClumpFootprint.EMPTY;
} }
for (int cell = 0; cell < cells.count; cell++) { 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); void add(int x, int y, int z);
} }
public record ClumpFootprint(int width, int minY, boolean empty) { public static final class ClumpFootprint {
static final ClumpFootprint EMPTY = new ClumpFootprint(1, 0, true); 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<NativeBlockState> palette;
private ClumpFootprint(int width, int minY, FootprintCells source, long stamp, KList<NativeBlockState> 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 { private static final class FootprintCells implements CellSink {
@@ -639,10 +708,14 @@ public class IrisDepositGenerator {
private static final int AXIS = 64; private static final int AXIS = 64;
private final long[] occupied = new long[AXIS * AXIS * AXIS / 64]; private final long[] occupied = new long[AXIS * AXIS * AXIS / 64];
private final int[] touched = new int[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 touchedCount;
private int count; private int count;
private int extentX; private int extentX;
private int minY; private int minY;
private long stamp;
private void reset() { private void reset() {
for (int index = 0; index < touchedCount; index++) { for (int index = 0; index < touchedCount; index++) {
@@ -652,6 +725,7 @@ public class IrisDepositGenerator {
count = 0; count = 0;
extentX = 0; extentX = 0;
minY = Integer.MAX_VALUE; minY = Integer.MAX_VALUE;
stamp++;
} }
@Override @Override
@@ -667,9 +741,29 @@ public class IrisDepositGenerator {
touched[touchedCount++] = word; touched[touchedCount++] = word;
} }
occupied[word] = current | bit; 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)); extentX = Math.max(extentX, Math.abs(x));
minY = Math.min(minY, y); minY = Math.min(minY, y);
} }
private ClumpCells toCells(KList<NativeBlockState> 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);
}
} }
} }
@@ -52,14 +52,14 @@ import art.arcane.iris.pack.loading.ResourceLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Predicate;
public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState> { public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState> {
private static final int CLUMPS_PER_BATCH = 8; private static final int CLUMPS_PER_BATCH = 8;
private static final int PREPARATION_BATCH_COUNT = 4; private static final int PREPARATION_BATCH_COUNT = 4;
private final RNG rng; private final RNG rng;
private volatile DepositVariantScan variantScan; private volatile PackScan packScan;
public IrisDepositModifier(Engine engine) { public IrisDepositModifier(Engine engine) {
super(engine, "Deposit"); super(engine, "Deposit");
@@ -87,6 +87,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
BurstExecutor burst = burst().burst(multicore); BurstExecutor burst = burst().burst(multicore);
long seed = x * 341873128712L + z * 132897987541L; long seed = x * 341873128712L + z * 132897987541L;
int highestSurface = highestTerrainSurface(x, z, null, context);
PreparedDeposit[] prepared = new PreparedDeposit[PREPARATION_BATCH_COUNT]; PreparedDeposit[] prepared = new PreparedDeposit[PREPARATION_BATCH_COUNT];
int pending = 0; int pending = 0;
IrisEngine generationEngine = getEngine() instanceof IrisEngine irisEngine IrisEngine generationEngine = getEngine() instanceof IrisEngine irisEngine
@@ -103,7 +104,8 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH); int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
int index = pending++; int index = pending++;
burst.queue(scopedDepositTask( burst.queue(scopedDepositTask(
() -> 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) { if (pending == prepared.length) {
completeBatches(burst, prepared, chunk, terrain, x, z, preparation); completeBatches(burst, prepared, chunk, terrain, x, z, preparation);
pending = 0; pending = 0;
@@ -172,7 +174,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
DepositPlan plan = plan(k, rng, context); DepositPlan plan = plan(k, rng, context);
for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) { for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) {
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH); int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
place(prepare(plan, first, limit, cx, cz, he, context), chunk, data, cx, cz, he, context); place(prepare(plan, first, limit, cx, cz, he, context, Integer.MIN_VALUE), chunk, data, cx, cz, he, context);
} }
} }
@@ -213,21 +215,27 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
return false; return false;
} }
private PreparedDeposit prepare(DepositPlan plan, int first, int limit, int cx, int cz, HeightMap he, ChunkContext context) { private PreparedDeposit prepare(DepositPlan plan, int first, int limit, int cx, int cz, HeightMap he,
ChunkContext context, int highestSurface) {
IrisDepositGenerator k = plan.generator(); IrisDepositGenerator k = plan.generator();
boolean oreDeposit = plan.ore(); boolean oreDeposit = plan.ore();
boolean needsCaveBiome = oreDeposit || k.usesCaveBiomeFilter(); boolean scalable = oreDeposit && packScan().oreScaling();
boolean needsCaveBiome = scalable || k.usesCaveBiomeFilter();
IrisDimensionCarvingResolver.State carvingState = needsCaveBiome ? new IrisDimensionCarvingResolver.State() : null; IrisDimensionCarvingResolver.State carvingState = needsCaveBiome ? new IrisDimensionCarvingResolver.State() : null;
boolean terrainScoped = k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN;
int highest = highestSurface;
int engineHeight = getEngine().getHeight();
List<PreparedClump> clumps = new ArrayList<>(limit - first); List<PreparedClump> clumps = new ArrayList<>(limit - first);
int terrainLimit = Integer.MIN_VALUE;
for (int l = first; l < limit; l++) { for (int l = first; l < limit; l++) {
long clumpSeed = clumpSeed(plan.seed(), l); RNG clumpRng = new RNG(clumpSeed(plan.seed(), l));
RNG clumpRng = new RNG(clumpSeed);
if (k.getPerClumpSpawnChance() < clumpRng.d()) { if (k.getPerClumpSpawnChance() < clumpRng.d()) {
continue; continue;
} }
IrisDepositGenerator.ClumpFootprint footprint = k.sampleClumpFootprint(clumpRng, getData()); IrisDepositGenerator.ClumpFootprint footprint = k.sampleClumpFootprint(clumpRng, getData());
if (footprint != null && footprint.empty() && !scalable) {
continue;
}
IrisObject clump = footprint == null ? k.getClump(getEngine(), clumpRng, getData()) : null; IrisObject clump = footprint == null ? k.getClump(getEngine(), clumpRng, getData()) : null;
int dim = footprint == null ? clump.getW() : footprint.width(); int dim = footprint == null ? clump.getW() : footprint.width();
@@ -242,9 +250,9 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
int x = clumpRng.i(min, max + 1); int x = clumpRng.i(min, max + 1);
int z = clumpRng.i(min, max + 1); int z = clumpRng.i(min, max + 1);
int terrainSurface = getDepositTerrainSurface(cx, cz, x, z, he, context); int terrainSurface = getDepositTerrainSurface(cx, cz, x, z, he, context);
int height = k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN int height = terrainScoped
? depositSurfaceLimit(terrainSurface, k.getSurfaceClearance()) ? depositSurfaceLimit(terrainSurface, k.getSurfaceClearance())
: getEngine().getHeight() - 1; : engineHeight - 1;
if (height < 0) { if (height < 0) {
continue; continue;
@@ -252,7 +260,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
int y = sampleHeight( int y = sampleHeight(
k.getHeightDistribution(), clumpRng, k.getMinHeight(), k.getMaxHeight(), k.getHeightDistribution(), clumpRng, k.getMinHeight(), k.getMaxHeight(),
Math.min(height, getEngine().getHeight() - 1)); Math.min(height, engineHeight - 1));
if (y == Integer.MIN_VALUE) { if (y == Integer.MIN_VALUE) {
continue; continue;
} }
@@ -262,7 +270,16 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
continue; continue;
} }
int biomeY = Math.max(0, Math.min(getEngine().getHeight() - 1, y)); boolean footprintLimited = footprint != null && terrainScoped && !footprint.empty();
if (footprintLimited && highest == Integer.MIN_VALUE) {
highest = highestTerrainSurface(cx, cz, he, context);
}
int terrainLimit = highest - Math.max(0, k.getSurfaceClearance());
if (footprintLimited && !scalable && y + footprint.minY() > terrainLimit) {
continue;
}
int biomeY = Math.max(0, Math.min(engineHeight - 1, y));
IrisBiome surfaceBiome = context.getBiome().get(x, z); IrisBiome surfaceBiome = context.getBiome().get(x, z);
IrisBiome depositBiome = needsCaveBiome IrisBiome depositBiome = needsCaveBiome
? getEngine().getCaveBiome( ? getEngine().getCaveBiome(
@@ -272,43 +289,34 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
continue; continue;
} }
if (oreDeposit) { if (oreDeposit && depositBiome != null) {
if (depositBiome != null) { double frequencyMultiplier = depositBiome.getOreDepositFrequencyMultiplier();
double frequencyMultiplier = depositBiome.getOreDepositFrequencyMultiplier(); if (frequencyMultiplier < 1D
if (frequencyMultiplier < 1D && !passesOreFrequency(frequencyMultiplier, clumpRng.d())) {
&& !passesOreFrequency(frequencyMultiplier, clumpRng.d())) { continue;
continue; }
}
double sizeMultiplier = depositBiome.getOreDepositSizeMultiplier(); double sizeMultiplier = depositBiome.getOreDepositSizeMultiplier();
if (sizeMultiplier != 1D) { if (sizeMultiplier != 1D) {
IrisObject scaledClump = k.getClump(getEngine(), clumpRng, getData(), sizeMultiplier); IrisObject scaledClump = k.getClump(getEngine(), clumpRng, getData(), sizeMultiplier);
int scaledDimension = scaledClump.getW(); int scaledDimension = scaledClump.getW();
x = clampDepositCenter(x, scaledDimension, 16); x = clampDepositCenter(x, scaledDimension, 16);
if (clippedHeight) { if (clippedHeight) {
y = clampDepositCenter(y, scaledDimension, getEngine().getHeight()); y = clampDepositCenter(y, scaledDimension, engineHeight);
}
z = clampDepositCenter(z, scaledDimension, 16);
clump = scaledClump;
} }
z = clampDepositCenter(z, scaledDimension, 16);
clump = scaledClump;
} }
} }
if (clump == null) { if (clump != null) {
if (k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN && !footprint.empty()) { clumps.add(new PreparedClump(clump, null, x, y, z, clumpRng));
if (terrainLimit == Integer.MIN_VALUE) { continue;
terrainLimit = chunkTerrainLimit(cx, cz, he, context, k.getSurfaceClearance());
}
if (y + footprint.minY() > terrainLimit) {
continue;
}
}
RNG replay = new RNG(clumpSeed);
replay.d();
clump = k.getClump(getEngine(), replay, getData());
} }
if (footprint.empty() || footprintLimited && y + footprint.minY() > terrainLimit) {
clumps.add(new PreparedClump(clump, x, y, z, clumpRng)); continue;
}
clumps.add(new PreparedClump(null, footprint.cells(), x, y, z, clumpRng));
} }
return new PreparedDeposit(k, oreDeposit, clumps); return new PreparedDeposit(k, oreDeposit, clumps);
} }
@@ -318,72 +326,24 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
if (deposit.clumps().isEmpty()) { if (deposit.clumps().isEmpty()) {
return; return;
} }
IrisDepositGenerator k = deposit.generator(); ClumpPlacement placement = new ClumpPlacement(deposit.generator(), deposit.ore(), chunk, data, cx, cz, he,
boolean oreDeposit = deposit.ore(); context, depositVariantsPossible());
IrisDimensionCarvingResolver.State carvingState = new IrisDimensionCarvingResolver.State();
IrisDimension dimension = getDimension();
boolean variants = depositVariantsPossible();
for (PreparedClump prepared : deposit.clumps()) { for (PreparedClump prepared : deposit.clumps()) {
IrisObject clump = prepared.object();
int x = prepared.x(); int x = prepared.x();
int y = prepared.y(); int y = prepared.y();
int z = prepared.z(); int z = prepared.z();
RNG rng = prepared.rng(); RNG rng = prepared.rng();
IrisDepositGenerator.ClumpCells cells = prepared.cells();
VectorMap<NativeBlockState>.Cursor cursor = clump.getBlocks().cursor(); 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<NativeBlockState>.Cursor cursor = prepared.object().getBlocks().cursor();
while (cursor.next()) { while (cursor.next()) {
IrisBlockVector j = cursor.key(); IrisBlockVector j = cursor.key();
int nx = j.getBlockX() + x; placement.place(j.getBlockX() + x, j.getBlockY() + y, j.getBlockZ() + z, cursor.value(), rng);
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);
}
} }
} }
} }
@@ -397,17 +357,17 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
} }
/** /**
* The highest block a TERRAIN-scoped deposit may place anywhere in the chunk; a clump whose lowest block sits * The highest terrain surface in the chunk. A TERRAIN-scoped clump whose lowest block sits above it, less the
* above it would have every block rejected by {@link #placementSurfaceAllows}. * generator's clearance, would have every block rejected by {@link #placementSurfaceAllows}.
*/ */
private int chunkTerrainLimit(int cx, int cz, HeightMap he, ChunkContext context, int surfaceClearance) { private int highestTerrainSurface(int cx, int cz, HeightMap he, ChunkContext context) {
int highest = Integer.MIN_VALUE; int highest = Integer.MIN_VALUE;
for (int x = 0; x < 16; x++) { for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
highest = Math.max(highest, getDepositTerrainSurface(cx, cz, x, z, he, context)); highest = Math.max(highest, getDepositTerrainSurface(cx, cz, x, z, he, context));
} }
} }
return highest - Math.max(0, surfaceClearance); return highest;
} }
static int depositSurfaceLimit(int surfaceY) { static int depositSurfaceLimit(int surfaceY) {
@@ -532,39 +492,52 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
* enumerate (stacked dimensions, unreadable loaders) keeps the lookup. * enumerate (stacked dimensions, unreadable loaders) keeps the lookup.
*/ */
private boolean depositVariantsPossible() { private boolean depositVariantsPossible() {
return packScan().variants();
}
private PackScan packScan() {
IrisData data = getData(); IrisData data = getData();
DepositVariantScan scan = variantScan; PackScan scan = packScan;
if (scan == null || scan.data() != data) { if (scan == null || scan.data() != data) {
scan = new DepositVariantScan(data, scanDepositVariants(data)); scan = scanPack(data);
variantScan = scan; packScan = scan;
} }
return scan.possible(); return scan;
} }
private boolean scanDepositVariants(IrisData data) { /**
* Ore clumps resolve their cave biome only to apply its ore frequency and size multipliers; when no biome of
* the loaded pack changes either, that lookup can never alter a clump and is skipped.
*/
private PackScan scanPack(IrisData data) {
IrisDimension dimension = getDimension(); IrisDimension dimension = getDimension();
if (data == null || dimension == null || getEngine().getDimensionStackContext() != null if (data == null || dimension == null || getEngine().getDimensionStackContext() != null) {
|| dimension.getDepositVariants() == null || !dimension.getDepositVariants().isEmpty()) { return new PackScan(data, true, true);
return true;
} }
Boolean regions = declaresVariants(data.getRegionLoader(), IrisRegion::getDepositVariants); Boolean regionVariants = anyLoaded(data.getRegionLoader(), region -> declaresVariants(region.getDepositVariants()));
Boolean biomes = declaresVariants(data.getBiomeLoader(), IrisBiome::getDepositVariants); Boolean biomeVariants = anyLoaded(data.getBiomeLoader(), biome -> declaresVariants(biome.getDepositVariants()));
return regions == null || regions || biomes == null || biomes; 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 <T extends IrisRegistrant> Boolean declaresVariants(ResourceLoader<T> loader, private static boolean declaresVariants(List<IrisDepositVariant> declared) {
Function<T, ? extends List<IrisDepositVariant>> variants) { return declared == null || !declared.isEmpty();
}
private static boolean scalesOreDeposits(IrisBiome biome) {
return biome.getOreDepositFrequencyMultiplier() < 1D || biome.getOreDepositSizeMultiplier() != 1D;
}
private static <T extends IrisRegistrant> Boolean anyLoaded(ResourceLoader<T> loader, Predicate<T> test) {
String[] keys = loader == null ? null : loader.getPossibleKeys(); String[] keys = loader == null ? null : loader.getPossibleKeys();
if (keys == null) { if (keys == null) {
return null; return null;
} }
for (String key : keys) { for (String key : keys) {
T loaded = loader.load(key); T loaded = loader.load(key);
if (loaded == null) { if (loaded != null && test.test(loaded)) {
continue;
}
List<IrisDepositVariant> declared = variants.apply(loaded);
if (declared == null || !declared.isEmpty()) {
return true; return true;
} }
} }
@@ -659,12 +632,95 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
private record DepositPlan(IrisDepositGenerator generator, long seed, boolean ore, int attempts) { private record DepositPlan(IrisDepositGenerator generator, long seed, boolean ore, int attempts) {
} }
private record DepositVariantScan(IrisData data, boolean possible) { private record PackScan(IrisData data, boolean variants, boolean oreScaling) {
} }
private record PreparedDeposit(IrisDepositGenerator generator, boolean ore, List<PreparedClump> clumps) { private record PreparedDeposit(IrisDepositGenerator generator, boolean ore, List<PreparedClump> 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<NativeBlockState> 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<NativeBlockState> 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));
}
}
} }
} }
@@ -16,6 +16,8 @@ import org.junit.Test;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -258,6 +260,40 @@ public class IrisDepositTuningTest {
assertNull(generator.sampleClumpFootprint(new RNG(1L), data)); 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<String> expected = new ArrayList<>();
VectorMap<NativeBlockState>.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<String> 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 @Test
public void vanillaScatteredSizeIsAnUpperCandidateBound() { public void vanillaScatteredSizeIsAnUpperCandidateBound() {
IrisData data = mock(IrisData.class); IrisData data = mock(IrisData.class);
@@ -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 @Test
public void unmatchableSurfaceBiomeFiltersSkipClumpsWithoutChangingOutput() { public void unmatchableSurfaceBiomeFiltersSkipClumpsWithoutChangingOutput() {
long[] expected = {-681153659952315909L, 784L, 2118L, 53L, 67L, 10L}; long[] expected = {-681153659952315909L, 784L, 2118L, 53L, 67L, 10L};
@@ -126,9 +138,15 @@ public class IrisDepositModifierParityTest {
return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, null); return generateConfigured(seed, multicore, caveVariant, enumerablePack, caveBiomeLookups, null);
} }
@SuppressWarnings("unchecked")
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack, private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps) { 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)); Engine engine = mock(Engine.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS)); ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
IrisDimension dimension = new IrisDimension(); IrisDimension dimension = new IrisDimension();
@@ -136,8 +154,10 @@ public class IrisDepositModifierParityTest {
IrisBiome surface = new IrisBiome(); IrisBiome surface = new IrisBiome();
IrisBiome cave = new IrisBiome(); IrisBiome cave = new IrisBiome();
cave.setLoadKey("allowed"); cave.setLoadKey("allowed");
cave.setOreDepositFrequencyMultiplier(0.65D); if (oreScaling) {
cave.setOreDepositSizeMultiplier(1.5D); cave.setOreDepositFrequencyMultiplier(0.65D);
cave.setOreDepositSizeMultiplier(1.5D);
}
IrisBiome denied = new IrisBiome(); IrisBiome denied = new IrisBiome();
denied.setLoadKey("denied"); denied.setLoadKey("denied");
IrisDepositVariant variant = new IrisDepositVariant().setMinHeight(-64).setMaxHeight(64); IrisDepositVariant variant = new IrisDepositVariant().setMinHeight(-64).setMaxHeight(64);