mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Reject deposit clumps early
- Ellipsoid footprint dry run replays the build draws without building - Terrain-scoped clumps above every column limit are dropped before building - Surface biome filters no chunk column passes skip the deposit
This commit is contained in:
1 parent
b47c7ae6aa
commit
ec1984949b
4 files changed
+237
-13
No files matched your search
@@ -53,6 +53,8 @@ import lombok.experimental.Accessors;
|
||||
@Description("Creates ore & other block deposits underground")
|
||||
@Data
|
||||
public class IrisDepositGenerator {
|
||||
private static final int FOOTPRINT_MAXIMUM_SIZE = 64;
|
||||
private static final ThreadLocal<FootprintCells> FOOTPRINT_CELLS = ThreadLocal.withInitial(FootprintCells::new);
|
||||
private static final long FNV_OFFSET_BASIS = 0xcbf29ce484222325L;
|
||||
private static final long FNV_PRIME = 0x100000001b3L;
|
||||
|
||||
@@ -267,6 +269,42 @@ public class IrisDepositGenerator {
|
||||
return new IrisObject(1, 1, 1);
|
||||
}
|
||||
|
||||
KSet<BlockPosition> cells = new KSet<>();
|
||||
vanillaEllipsoidCells(rng, size, (x, y, z) -> cells.add(new BlockPosition(x, y, z)));
|
||||
return objectFromCells(cells, rng, rdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances {@code rng} exactly as {@link #getClump(Engine, RNG, IrisData)} would and reports the clump's width
|
||||
* and lowest block offset without building it, so a caller can reject the clump first and replay the build
|
||||
* only for clumps it keeps. Returns null without drawing when the shape has no footprint path.
|
||||
*/
|
||||
public ClumpFootprint sampleClumpFootprint(RNG rng, IrisData rdata) {
|
||||
if (shape != IrisDepositShape.VANILLA_ELLIPSOID || minSize > FOOTPRINT_MAXIMUM_SIZE
|
||||
|| maxSize > FOOTPRINT_MAXIMUM_SIZE) {
|
||||
return null;
|
||||
}
|
||||
int paletteSize = getBlockData(rdata).size();
|
||||
if (paletteSize == 0) {
|
||||
return null;
|
||||
}
|
||||
int size = rng.i(minSize, maxSize + 1);
|
||||
if (size <= 0) {
|
||||
return ClumpFootprint.EMPTY;
|
||||
}
|
||||
FootprintCells cells = FOOTPRINT_CELLS.get();
|
||||
cells.reset();
|
||||
vanillaEllipsoidCells(rng, size, cells);
|
||||
if (cells.count == 0) {
|
||||
return ClumpFootprint.EMPTY;
|
||||
}
|
||||
for (int cell = 0; cell < cells.count; cell++) {
|
||||
rng.i(0, paletteSize);
|
||||
}
|
||||
return new ClumpFootprint(cells.extentX * 2 + 1, cells.minY, false);
|
||||
}
|
||||
|
||||
private static void vanillaEllipsoidCells(RNG rng, int size, CellSink sink) {
|
||||
float angle = rng.nextFloat() * (float) Math.PI;
|
||||
float reach = size / 8F;
|
||||
double startX = Math.sin(angle) * reach;
|
||||
@@ -309,7 +347,6 @@ public class IrisDepositGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
KSet<BlockPosition> cells = new KSet<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
double radius = nodes[i * 4 + 3];
|
||||
if (radius < 0D) {
|
||||
@@ -338,14 +375,12 @@ public class IrisDepositGenerator {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
double nz = (z + 0.5D - centerZ) / radius;
|
||||
if (nx * nx + ny * ny + nz * nz < 1D) {
|
||||
cells.add(new BlockPosition(x, y, z));
|
||||
sink.add(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objectFromCells(cells, rng, rdata);
|
||||
}
|
||||
|
||||
IrisObject generateVanillaScattered(RNG rng, IrisData rdata, int size) {
|
||||
@@ -564,4 +599,52 @@ public class IrisDepositGenerator {
|
||||
|
||||
record ClumpCacheKey(long depositSeed, int minSize, int maxSize) {
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface CellSink {
|
||||
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);
|
||||
}
|
||||
|
||||
private static final class FootprintCells implements CellSink {
|
||||
private static final int OFFSET = 32;
|
||||
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 touchedCount;
|
||||
private int count;
|
||||
private int extentX;
|
||||
private int minY;
|
||||
|
||||
private void reset() {
|
||||
for (int index = 0; index < touchedCount; index++) {
|
||||
occupied[touched[index]] = 0L;
|
||||
}
|
||||
touchedCount = 0;
|
||||
count = 0;
|
||||
extentX = 0;
|
||||
minY = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int x, int y, int z) {
|
||||
int index = ((x + OFFSET) * AXIS + (y + OFFSET)) * AXIS + (z + OFFSET);
|
||||
int word = index >>> 6;
|
||||
long bit = 1L << (index & 63);
|
||||
long current = occupied[word];
|
||||
if ((current & bit) != 0L) {
|
||||
return;
|
||||
}
|
||||
if (current == 0L) {
|
||||
touched[touchedCount++] = word;
|
||||
}
|
||||
occupied[word] = current | bit;
|
||||
count++;
|
||||
extentX = Math.max(extentX, Math.abs(x));
|
||||
minY = Math.min(minY, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import art.arcane.iris.generation.runtime.IrisEngine;
|
||||
import art.arcane.iris.generation.runtime.EngineAssignedModifier;
|
||||
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositBiomeScope;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositHeightDistribution;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositPlacementScope;
|
||||
@@ -96,7 +97,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
Throwable callerFailure = null;
|
||||
try {
|
||||
for (int i = 0; i < generators.size(); i++) {
|
||||
DepositPlan plan = plan(generators.get(i), rng.nextParallelRNG(seed * (i + 1L)));
|
||||
DepositPlan plan = plan(generators.get(i), rng.nextParallelRNG(seed * (i + 1L)), context);
|
||||
for (int first = 0; first < plan.attempts(); first += CLUMPS_PER_BATCH) {
|
||||
int firstAttempt = first;
|
||||
int limit = Math.min(plan.attempts(), first + CLUMPS_PER_BATCH);
|
||||
@@ -168,20 +169,48 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
}
|
||||
|
||||
public void generate(IrisDepositGenerator k, MantleChunk chunk, Hunk<NativeBlockState> data, RNG rng, int cx, int cz, boolean safe, HeightMap he, ChunkContext context) {
|
||||
DepositPlan plan = plan(k, rng);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private DepositPlan plan(IrisDepositGenerator generator, RNG rng) {
|
||||
private DepositPlan plan(IrisDepositGenerator generator, RNG rng, ChunkContext context) {
|
||||
if (generator.getSpawnChance() < rng.d()) {
|
||||
return new DepositPlan(generator, rng.getSeed(), false, 0);
|
||||
}
|
||||
boolean ore = generator.isOre(getData());
|
||||
int attempts = rng.i(generator.getMinPerChunk(), generator.getMaxPerChunk() + 1);
|
||||
return new DepositPlan(generator, rng.getSeed(), ore, attempts);
|
||||
return new DepositPlan(generator, rng.getSeed(), ore,
|
||||
attempts == 0 || surfaceBiomeMayMatch(generator, context) ? attempts : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* A surface-scoped biome filter reads the surface biome of the clump's own column, so when no column of the
|
||||
* chunk passes it every clump is rejected at that check. Each clump draws from its own seeded RNG, so skipping
|
||||
* them all up front changes nothing else.
|
||||
*/
|
||||
private static boolean surfaceBiomeMayMatch(IrisDepositGenerator generator, ChunkContext context) {
|
||||
if (generator.getBiomeScope() != IrisDepositBiomeScope.SURFACE
|
||||
|| (generator.getIncludedBiomes() == null || generator.getIncludedBiomes().isEmpty())
|
||||
&& (generator.getExcludedBiomes() == null || generator.getExcludedBiomes().isEmpty())) {
|
||||
return true;
|
||||
}
|
||||
IrisBiome checked = null;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
IrisBiome surface = context.getBiome().get(x, z);
|
||||
if (surface == checked) {
|
||||
continue;
|
||||
}
|
||||
if (generator.matchesBiome(surface, null)) {
|
||||
return true;
|
||||
}
|
||||
checked = surface;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private PreparedDeposit prepare(DepositPlan plan, int first, int limit, int cx, int cz, HeightMap he, ChunkContext context) {
|
||||
@@ -190,15 +219,18 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
boolean needsCaveBiome = oreDeposit || k.usesCaveBiomeFilter();
|
||||
IrisDimensionCarvingResolver.State carvingState = needsCaveBiome ? new IrisDimensionCarvingResolver.State() : null;
|
||||
List<PreparedClump> clumps = new ArrayList<>(limit - first);
|
||||
int terrainLimit = Integer.MIN_VALUE;
|
||||
for (int l = first; l < limit; l++) {
|
||||
RNG clumpRng = new RNG(clumpSeed(plan.seed(), l));
|
||||
long clumpSeed = clumpSeed(plan.seed(), l);
|
||||
RNG clumpRng = new RNG(clumpSeed);
|
||||
if (k.getPerClumpSpawnChance() < clumpRng.d()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IrisObject clump = k.getClump(getEngine(), clumpRng, getData());
|
||||
IrisDepositGenerator.ClumpFootprint footprint = k.sampleClumpFootprint(clumpRng, getData());
|
||||
IrisObject clump = footprint == null ? k.getClump(getEngine(), clumpRng, getData()) : null;
|
||||
|
||||
int dim = clump.getW();
|
||||
int dim = footprint == null ? clump.getW() : footprint.width();
|
||||
int min = dim / 2;
|
||||
int max = (int) (16D - dim / 2D);
|
||||
|
||||
@@ -262,6 +294,20 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
clumps.add(new PreparedClump(clump, x, y, z, clumpRng));
|
||||
}
|
||||
return new PreparedDeposit(k, oreDeposit, clumps);
|
||||
@@ -350,6 +396,20 @@ public class IrisDepositModifier extends EngineAssignedModifier<NativeBlockState
|
||||
: context.getRoundedHeight(localX, localZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
private int chunkTerrainLimit(int cx, int cz, HeightMap he, ChunkContext context, int surfaceClearance) {
|
||||
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);
|
||||
}
|
||||
|
||||
static int depositSurfaceLimit(int surfaceY) {
|
||||
return depositSurfaceLimit(surfaceY, 7);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user