mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +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.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);
|
||||||
|
|||||||
+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
|
@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);
|
||||||
|
|||||||
Reference in new issue
Block a user