mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
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:
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.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<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 {
|
||||
@@ -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<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.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState> {
|
||||
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<NativeBlockState
|
||||
|
||||
BurstExecutor burst = burst().burst(multicore);
|
||||
long seed = x * 341873128712L + z * 132897987541L;
|
||||
int highestSurface = highestTerrainSurface(x, z, null, context);
|
||||
PreparedDeposit[] prepared = new PreparedDeposit[PREPARATION_BATCH_COUNT];
|
||||
int pending = 0;
|
||||
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 index = pending++;
|
||||
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) {
|
||||
completeBatches(burst, prepared, chunk, terrain, x, z, preparation);
|
||||
pending = 0;
|
||||
@@ -172,7 +174,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
DepositPlan plan = plan(k, rng, context);
|
||||
for (int first = 0; first < 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;
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
boolean terrainScoped = k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN;
|
||||
int highest = highestSurface;
|
||||
int engineHeight = getEngine().getHeight();
|
||||
List<PreparedClump> 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<NativeBlockState
|
||||
int x = clumpRng.i(min, max + 1);
|
||||
int z = clumpRng.i(min, max + 1);
|
||||
int terrainSurface = getDepositTerrainSurface(cx, cz, x, z, he, context);
|
||||
int height = k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN
|
||||
int height = terrainScoped
|
||||
? depositSurfaceLimit(terrainSurface, k.getSurfaceClearance())
|
||||
: getEngine().getHeight() - 1;
|
||||
: engineHeight - 1;
|
||||
|
||||
if (height < 0) {
|
||||
continue;
|
||||
@@ -252,7 +260,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
|
||||
int y = sampleHeight(
|
||||
k.getHeightDistribution(), clumpRng, k.getMinHeight(), k.getMaxHeight(),
|
||||
Math.min(height, getEngine().getHeight() - 1));
|
||||
Math.min(height, engineHeight - 1));
|
||||
if (y == Integer.MIN_VALUE) {
|
||||
continue;
|
||||
}
|
||||
@@ -262,7 +270,16 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
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 depositBiome = needsCaveBiome
|
||||
? getEngine().getCaveBiome(
|
||||
@@ -272,43 +289,34 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oreDeposit) {
|
||||
if (depositBiome != null) {
|
||||
double frequencyMultiplier = depositBiome.getOreDepositFrequencyMultiplier();
|
||||
if (frequencyMultiplier < 1D
|
||||
&& !passesOreFrequency(frequencyMultiplier, clumpRng.d())) {
|
||||
continue;
|
||||
}
|
||||
if (oreDeposit && depositBiome != null) {
|
||||
double frequencyMultiplier = depositBiome.getOreDepositFrequencyMultiplier();
|
||||
if (frequencyMultiplier < 1D
|
||||
&& !passesOreFrequency(frequencyMultiplier, clumpRng.d())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double sizeMultiplier = depositBiome.getOreDepositSizeMultiplier();
|
||||
if (sizeMultiplier != 1D) {
|
||||
IrisObject scaledClump = k.getClump(getEngine(), clumpRng, getData(), sizeMultiplier);
|
||||
int scaledDimension = scaledClump.getW();
|
||||
x = clampDepositCenter(x, scaledDimension, 16);
|
||||
if (clippedHeight) {
|
||||
y = clampDepositCenter(y, scaledDimension, getEngine().getHeight());
|
||||
}
|
||||
z = clampDepositCenter(z, scaledDimension, 16);
|
||||
clump = scaledClump;
|
||||
double sizeMultiplier = depositBiome.getOreDepositSizeMultiplier();
|
||||
if (sizeMultiplier != 1D) {
|
||||
IrisObject scaledClump = k.getClump(getEngine(), clumpRng, getData(), sizeMultiplier);
|
||||
int scaledDimension = scaledClump.getW();
|
||||
x = clampDepositCenter(x, scaledDimension, 16);
|
||||
if (clippedHeight) {
|
||||
y = clampDepositCenter(y, scaledDimension, engineHeight);
|
||||
}
|
||||
z = clampDepositCenter(z, scaledDimension, 16);
|
||||
clump = scaledClump;
|
||||
}
|
||||
}
|
||||
|
||||
if (clump == null) {
|
||||
if (k.getPlacementScope() == IrisDepositPlacementScope.TERRAIN && !footprint.empty()) {
|
||||
if (terrainLimit == Integer.MIN_VALUE) {
|
||||
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 (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<NativeBlockState
|
||||
if (deposit.clumps().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
IrisDepositGenerator k = deposit.generator();
|
||||
boolean oreDeposit = deposit.ore();
|
||||
IrisDimensionCarvingResolver.State carvingState = new IrisDimensionCarvingResolver.State();
|
||||
IrisDimension dimension = getDimension();
|
||||
boolean variants = depositVariantsPossible();
|
||||
ClumpPlacement placement = new ClumpPlacement(deposit.generator(), deposit.ore(), chunk, data, cx, cz, he,
|
||||
context, depositVariantsPossible());
|
||||
for (PreparedClump prepared : deposit.clumps()) {
|
||||
IrisObject clump = prepared.object();
|
||||
int x = prepared.x();
|
||||
int y = prepared.y();
|
||||
int z = prepared.z();
|
||||
RNG rng = prepared.rng();
|
||||
|
||||
VectorMap<NativeBlockState>.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<NativeBlockState>.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<NativeBlockState
|
||||
}
|
||||
|
||||
/**
|
||||
* The highest block a TERRAIN-scoped deposit may place anywhere in the chunk; a clump whose lowest block sits
|
||||
* above it would have every block rejected by {@link #placementSurfaceAllows}.
|
||||
* The highest terrain surface in the chunk. A TERRAIN-scoped clump whose lowest block sits above it, less the
|
||||
* 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;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
highest = Math.max(highest, getDepositTerrainSurface(cx, cz, x, z, he, context));
|
||||
}
|
||||
}
|
||||
return highest - Math.max(0, surfaceClearance);
|
||||
return highest;
|
||||
}
|
||||
|
||||
static int depositSurfaceLimit(int surfaceY) {
|
||||
@@ -532,39 +492,52 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
* enumerate (stacked dimensions, unreadable loaders) keeps the lookup.
|
||||
*/
|
||||
private boolean depositVariantsPossible() {
|
||||
return packScan().variants();
|
||||
}
|
||||
|
||||
private PackScan packScan() {
|
||||
IrisData data = getData();
|
||||
DepositVariantScan scan = variantScan;
|
||||
PackScan scan = packScan;
|
||||
if (scan == null || scan.data() != data) {
|
||||
scan = new DepositVariantScan(data, scanDepositVariants(data));
|
||||
variantScan = scan;
|
||||
scan = scanPack(data);
|
||||
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();
|
||||
if (data == null || dimension == null || getEngine().getDimensionStackContext() != null
|
||||
|| dimension.getDepositVariants() == null || !dimension.getDepositVariants().isEmpty()) {
|
||||
return true;
|
||||
if (data == null || dimension == null || getEngine().getDimensionStackContext() != null) {
|
||||
return new PackScan(data, true, true);
|
||||
}
|
||||
Boolean regions = declaresVariants(data.getRegionLoader(), IrisRegion::getDepositVariants);
|
||||
Boolean biomes = declaresVariants(data.getBiomeLoader(), IrisBiome::getDepositVariants);
|
||||
return regions == null || regions || biomes == null || biomes;
|
||||
Boolean regionVariants = anyLoaded(data.getRegionLoader(), region -> 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 <T extends IrisRegistrant> Boolean declaresVariants(ResourceLoader<T> loader,
|
||||
Function<T, ? extends List<IrisDepositVariant>> variants) {
|
||||
private static boolean declaresVariants(List<IrisDepositVariant> declared) {
|
||||
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();
|
||||
if (keys == null) {
|
||||
return null;
|
||||
}
|
||||
for (String key : keys) {
|
||||
T loaded = loader.load(key);
|
||||
if (loaded == null) {
|
||||
continue;
|
||||
}
|
||||
List<IrisDepositVariant> 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<NativeBlockState
|
||||
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 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 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<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
|
||||
public void vanillaScatteredSizeIsAnUpperCandidateBound() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
|
||||
+23
-3
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user