mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Chunk-scoped cave lookups
- Subterrain cells for carve and decoration resolve through a per-chunk sampler with a column height hull instead of per-block engine dispatch - IrisEngine subterrain lookups read the thread frame and runtime once and memoize the dimension gate, removing the per-call disabled-component list scan - Unrouted workers resolve cave biomes through the captured carving snapshot and column cache with subterrain biomes checked first - Captured cave biome lookups bypass the per-voxel hash cache - Internal carve walls come from word-level column masks instead of hashed neighbor puts, painted after cheap solidity and surface checks - Floor and ceiling layers are memoized per column when the biome layers do not depend on the zone height - Zone scanning walks carved runs, the decoration scan memoizes block solidity and reuses the previous block, and waterlogging normalization skips snapshots without hydrology - Boundary walls read own cells straight from the snapshot; the unused snapshot face builder and face accessors are removed
This commit is contained in:
1 parent
af90765a92
commit
0bdb022f56
16 files changed
+790
-197
No files matched your search
@@ -144,20 +144,6 @@ public final class CaveTerrainSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MatterCavern[] composedFace(TerrainMatterView.Face face, int height) {
|
|
||||||
Objects.requireNonNull(face, "Terrain face");
|
|
||||||
if (height < 0) {
|
|
||||||
throw new IllegalArgumentException("Terrain face height must be nonnegative");
|
|
||||||
}
|
|
||||||
MatterCavern[] result = new MatterCavern[Math.multiplyExact(height, 16)];
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int offset = 0; offset < 16; offset++) {
|
|
||||||
result[y * 16 + offset] = composedCavern(face.x(offset), y, face.z(offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void captureSection(int section, Matter matter) {
|
private void captureSection(int section, Matter matter) {
|
||||||
caverns[section] = copy(matter.getSlice(MatterCavern.class), MatterCavern[]::new);
|
caverns[section] = copy(matter.getSlice(MatterCavern.class), MatterCavern[]::new);
|
||||||
hydrology[section] = copy(matter.getSlice(HydrologyCaveCell.class), HydrologyCaveCell[]::new);
|
hydrology[section] = copy(matter.getSlice(HydrologyCaveCell.class), HydrologyCaveCell[]::new);
|
||||||
|
|||||||
@@ -136,12 +136,8 @@ public final class TerrainMatterView {
|
|||||||
SOUTH(false, 15);
|
SOUTH(false, 15);
|
||||||
|
|
||||||
private final int[] positions = new int[256];
|
private final int[] positions = new int[256];
|
||||||
private final boolean fixedX;
|
|
||||||
private final int coordinate;
|
|
||||||
|
|
||||||
Face(boolean fixedX, int coordinate) {
|
Face(boolean fixedX, int coordinate) {
|
||||||
this.fixedX = fixedX;
|
|
||||||
this.coordinate = coordinate;
|
|
||||||
for (int y = 0; y < 16; y++) {
|
for (int y = 0; y < 16; y++) {
|
||||||
for (int offset = 0; offset < 16; offset++) {
|
for (int offset = 0; offset < 16; offset++) {
|
||||||
int x = fixedX ? coordinate : offset;
|
int x = fixedX ? coordinate : offset;
|
||||||
@@ -150,14 +146,6 @@ public final class TerrainMatterView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int x(int offset) {
|
|
||||||
return fixedX ? coordinate : offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
int z(int offset) {
|
|
||||||
return fixedX ? offset : coordinate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> void iterate(MantleChunk<Matter> chunk, Class<T> type,
|
public static <T> void iterate(MantleChunk<Matter> chunk, Class<T> type,
|
||||||
|
|||||||
@@ -342,12 +342,8 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
default SubterrainCell getSubterrainCell(int x, int y, int z) {
|
default SubterrainCell getSubterrainCell(int x, int y, int z) {
|
||||||
if (getComplex() == null || getDimension() == null
|
IrisComplex complex = getComplex();
|
||||||
|| !getDimension().allowsSubterrainFeatures()
|
SubterrainPlanner planner = complex == null ? null : SubterrainChunkCells.activePlanner(complex, getDimension());
|
||||||
|| getDimension().getSubterrainFeatures() == null || getDimension().getSubterrainFeatures().isEmpty()) {
|
|
||||||
return SubterrainCell.OUTSIDE;
|
|
||||||
}
|
|
||||||
SubterrainPlanner planner = getComplex().getSubterrainPlanner();
|
|
||||||
return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z);
|
return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package art.arcane.iris.generation.runtime;
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
import art.arcane.iris.generation.subterrain.SubterrainCell;
|
import art.arcane.iris.generation.subterrain.SubterrainCell;
|
||||||
|
import art.arcane.iris.generation.subterrain.SubterrainPlanner;
|
||||||
import art.arcane.iris.generation.runtime.EngineBackgroundTasks.BackgroundTaskDrain;
|
import art.arcane.iris.generation.runtime.EngineBackgroundTasks.BackgroundTaskDrain;
|
||||||
import art.arcane.iris.generation.runtime.EngineRuntimeBuilder.RuntimeAssembly;
|
import art.arcane.iris.generation.runtime.EngineRuntimeBuilder.RuntimeAssembly;
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
@@ -178,6 +179,7 @@ public class IrisEngine implements Engine {
|
|||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
private volatile boolean generationHistoryRoutingRequired;
|
private volatile boolean generationHistoryRoutingRequired;
|
||||||
|
private volatile SubterrainGate subterrainGate;
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
volatile EngineTarget publishedTarget;
|
volatile EngineTarget publishedTarget;
|
||||||
@@ -613,14 +615,55 @@ public class IrisEngine implements Engine {
|
|||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
@Override
|
@Override
|
||||||
public SubterrainCell getSubterrainCell(int x, int y, int z) {
|
public SubterrainCell getSubterrainCell(int x, int y, int z) {
|
||||||
if (!hasGenerationRuntimeScope() && generationHistoryRuntimeRouter != null) {
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
Optional<SubterrainCell> saved = generationHistoryRuntimeRouter.biomes()
|
GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter;
|
||||||
.subterrainCell(x, y + getWorld().minHeight(), z);
|
if ((frame == null || frame.binding == null) && router != null) {
|
||||||
|
Optional<SubterrainCell> saved = router.biomes()
|
||||||
|
.subterrainCell(x, y + targetIn(frame).getWorld().minHeight(), z);
|
||||||
if (saved.isPresent()) {
|
if (saved.isPresent()) {
|
||||||
return saved.get();
|
return saved.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Engine.super.getSubterrainCell(x, y, z);
|
if (frame == null || frame.assembly == null && (frame.environment == null || frame.binding != null)) {
|
||||||
|
GenerationRuntime current = selectedGenerationRuntime(frame);
|
||||||
|
if (current == null) {
|
||||||
|
return SubterrainCell.OUTSIDE;
|
||||||
|
}
|
||||||
|
SubterrainPlanner planner = admittedSubterrainPlanner(current.complex(), current.dimension());
|
||||||
|
return planner == null ? SubterrainCell.OUTSIDE
|
||||||
|
: planner.sample(x, y + current.target().getWorld().minHeight(), z);
|
||||||
|
}
|
||||||
|
SubterrainPlanner planner = subterrainPlannerIn(frame);
|
||||||
|
return planner == null ? SubterrainCell.OUTSIDE
|
||||||
|
: planner.sample(x, y + targetIn(frame).getWorld().minHeight(), z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
public SubterrainChunkCells subterrainChunkCells(int chunkX, int chunkZ) {
|
||||||
|
EngineThreadState.Frame frame = threadState.current();
|
||||||
|
if ((frame == null || frame.binding == null) && generationHistoryRuntimeRouter != null) {
|
||||||
|
return SubterrainChunkCells.delegated(this);
|
||||||
|
}
|
||||||
|
SubterrainPlanner planner = subterrainPlannerIn(frame);
|
||||||
|
return SubterrainChunkCells.planned(this, planner, chunkX, chunkZ,
|
||||||
|
planner == null ? 0 : targetIn(frame).getWorld().minHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubterrainPlanner subterrainPlannerIn(EngineThreadState.Frame frame) {
|
||||||
|
IrisComplex complex = complexIn(frame);
|
||||||
|
return complex == null ? null : admittedSubterrainPlanner(complex, dimensionIn(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubterrainPlanner admittedSubterrainPlanner(IrisComplex complex, IrisDimension dimension) {
|
||||||
|
if (complex == null || dimension == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SubterrainGate gate = subterrainGate;
|
||||||
|
if (gate == null || gate.dimension() != dimension) {
|
||||||
|
gate = new SubterrainGate(dimension, SubterrainChunkCells.admits(dimension));
|
||||||
|
subterrainGate = gate;
|
||||||
|
}
|
||||||
|
return gate.admitted() ? complex.getSubterrainPlanner() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
@@ -1036,6 +1079,10 @@ public class IrisEngine implements Engine {
|
|||||||
return threadState.binding() != null;
|
return threadState.binding() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasGenerationHistoryRouting() {
|
||||||
|
return generationHistoryRuntimeRouter != null || generationHistoryRoutingRequired;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean usesScopedNaturalTerrain() {
|
private boolean usesScopedNaturalTerrain() {
|
||||||
return hasGenerationRuntimeScope() && getComplex().isNaturalTerrainContext();
|
return hasGenerationRuntimeScope() && getComplex().isNaturalTerrainContext();
|
||||||
}
|
}
|
||||||
@@ -1333,7 +1380,10 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IrisComplex getComplex() {
|
public IrisComplex getComplex() {
|
||||||
EngineThreadState.Frame frame = threadState.current();
|
return complexIn(threadState.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisComplex complexIn(EngineThreadState.Frame frame) {
|
||||||
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null && assembly.complex != null) {
|
if (assembly != null && assembly.complex != null) {
|
||||||
return assembly.complex;
|
return assembly.complex;
|
||||||
@@ -1344,7 +1394,10 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EngineTarget getTarget() {
|
public EngineTarget getTarget() {
|
||||||
EngineThreadState.Frame frame = threadState.current();
|
return targetIn(threadState.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
private EngineTarget targetIn(EngineThreadState.Frame frame) {
|
||||||
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
RuntimeAssembly assembly = frame == null ? null : frame.assembly;
|
||||||
if (assembly != null) {
|
if (assembly != null) {
|
||||||
return assembly.target;
|
return assembly.target;
|
||||||
@@ -1372,7 +1425,10 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IrisDimension getDimension() {
|
public IrisDimension getDimension() {
|
||||||
EngineThreadState.Frame frame = threadState.current();
|
return dimensionIn(threadState.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisDimension dimensionIn(EngineThreadState.Frame frame) {
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
BiomeEnvironmentBinding environment = frame.environment;
|
BiomeEnvironmentBinding environment = frame.environment;
|
||||||
if (environment != null && frame.binding == null) {
|
if (environment != null && frame.binding == null) {
|
||||||
@@ -1892,4 +1948,6 @@ public class IrisEngine implements Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record SubterrainGate(IrisDimension dimension, boolean admitted) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.subterrain.SubterrainCell;
|
||||||
|
import art.arcane.iris.generation.subterrain.SubterrainPlanner;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class SubterrainChunkCells {
|
||||||
|
private final Engine engine;
|
||||||
|
private final boolean delegated;
|
||||||
|
private final SubterrainPlanner.ChunkSampler sampler;
|
||||||
|
private final int minimumY;
|
||||||
|
|
||||||
|
private SubterrainChunkCells(Engine engine, boolean delegated, SubterrainPlanner.ChunkSampler sampler, int minimumY) {
|
||||||
|
this.engine = Objects.requireNonNull(engine);
|
||||||
|
this.delegated = delegated;
|
||||||
|
this.sampler = sampler;
|
||||||
|
this.minimumY = minimumY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubterrainChunkCells of(Engine engine, int chunkX, int chunkZ) {
|
||||||
|
return engine instanceof IrisEngine irisEngine
|
||||||
|
? irisEngine.subterrainChunkCells(chunkX, chunkZ)
|
||||||
|
: delegated(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubterrainChunkCells delegated(Engine engine) {
|
||||||
|
return new SubterrainChunkCells(engine, true, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SubterrainChunkCells planned(Engine engine, SubterrainPlanner planner, int chunkX, int chunkZ, int minimumY) {
|
||||||
|
return new SubterrainChunkCells(engine, false, planner == null ? null : planner.chunkSampler(chunkX, chunkZ), minimumY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SubterrainPlanner activePlanner(IrisComplex complex, IrisDimension dimension) {
|
||||||
|
if (complex == null || dimension == null || !admits(dimension)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return complex.getSubterrainPlanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean admits(IrisDimension dimension) {
|
||||||
|
return dimension.allowsSubterrainFeatures()
|
||||||
|
&& dimension.getSubterrainFeatures() != null && !dimension.getSubterrainFeatures().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubterrainCell cell(int x, int y, int z) {
|
||||||
|
if (delegated) {
|
||||||
|
SubterrainCell cell = engine.getSubterrainCell(x, y, z);
|
||||||
|
return cell == null ? SubterrainCell.OUTSIDE : cell;
|
||||||
|
}
|
||||||
|
return sampler == null ? SubterrainCell.OUTSIDE : sampler.sample(x, y + minimumY, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IrisBiome biome(int x, int y, int z) {
|
||||||
|
if (delegated) {
|
||||||
|
return engine.getSubterrainBiome(x, y, z);
|
||||||
|
}
|
||||||
|
SubterrainCell cell = cell(x, y, z);
|
||||||
|
if (!cell.occupied() || cell.room().biome().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return engine.getSubterrainBiome(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,9 +20,7 @@ package art.arcane.iris.generation.stage;
|
|||||||
|
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
import art.arcane.iris.generation.subterrain.SubterrainCell;
|
import art.arcane.iris.generation.subterrain.SubterrainCell;
|
||||||
import art.arcane.iris.generation.subterrain.SubterrainPlan;
|
|
||||||
import art.arcane.iris.generation.subterrain.SubterrainPlanner;
|
import art.arcane.iris.generation.subterrain.SubterrainPlanner;
|
||||||
import java.util.List;
|
|
||||||
import art.arcane.iris.generation.subterrain.SubterrainRasterizer;
|
import art.arcane.iris.generation.subterrain.SubterrainRasterizer;
|
||||||
import art.arcane.iris.generation.subterrain.SubterrainSurfaceMaterials;
|
import art.arcane.iris.generation.subterrain.SubterrainSurfaceMaterials;
|
||||||
import art.arcane.iris.generation.runtime.DimensionStackContext;
|
import art.arcane.iris.generation.runtime.DimensionStackContext;
|
||||||
@@ -31,6 +29,7 @@ import art.arcane.iris.generation.runtime.UpperDimensionContext;
|
|||||||
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
|
import art.arcane.iris.generation.runtime.SubterrainChunkCells;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||||
@@ -39,6 +38,7 @@ import art.arcane.iris.generation.mantle.CaveTerrainSnapshot;
|
|||||||
import art.arcane.iris.generation.mantle.TerrainMatterView;
|
import art.arcane.iris.generation.mantle.TerrainMatterView;
|
||||||
import art.arcane.iris.generation.terrain.InferredType;
|
import art.arcane.iris.generation.terrain.InferredType;
|
||||||
import art.arcane.iris.generation.biome.IrisBiome;
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiomePaletteLayer;
|
||||||
import art.arcane.iris.generation.decoration.IrisDecorationPart;
|
import art.arcane.iris.generation.decoration.IrisDecorationPart;
|
||||||
import art.arcane.iris.generation.decoration.IrisDecorator;
|
import art.arcane.iris.generation.decoration.IrisDecorator;
|
||||||
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
||||||
@@ -101,7 +101,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
public void onModify(int x, int z, Hunk<NativeBlockState> output, boolean multicore, ChunkContext context) {
|
public void onModify(int x, int z, Hunk<NativeBlockState> output, boolean multicore, ChunkContext context) {
|
||||||
PrecisionStopwatch caveStopwatch = PrecisionStopwatch.start();
|
PrecisionStopwatch caveStopwatch = PrecisionStopwatch.start();
|
||||||
Mantle<Matter> mantle = getEngine().getMantle().getMantle();
|
Mantle<Matter> mantle = getEngine().getMantle().getMantle();
|
||||||
CaveInputs resolverState = new CaveInputs(getEngine());
|
CaveInputs resolverState = CaveInputs.forTerrainChunk(getEngine(), x, z);
|
||||||
IrisCarveScratch scratch = SCRATCH.get();
|
IrisCarveScratch scratch = SCRATCH.get();
|
||||||
scratch.reset();
|
scratch.reset();
|
||||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = scratch.caveBiomeCache;
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = scratch.caveBiomeCache;
|
||||||
@@ -144,6 +144,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
int worldHeightSpan = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
int worldHeightSpan = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
||||||
int caveLavaHeight = getEngine().getDimension().getCaveLavaHeight();
|
int caveLavaHeight = getEngine().getDimension().getCaveLavaHeight();
|
||||||
|
SubterrainPlanner subterrainPlanner = resolverState.complex.getSubterrainPlanner();
|
||||||
CarveResolutionContext resolutionContext = new CarveResolutionContext(
|
CarveResolutionContext resolutionContext = new CarveResolutionContext(
|
||||||
output,
|
output,
|
||||||
context,
|
context,
|
||||||
@@ -156,44 +157,28 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
caveLavaHeight,
|
caveLavaHeight,
|
||||||
chunkBlockX,
|
chunkBlockX,
|
||||||
chunkBlockZ,
|
chunkBlockZ,
|
||||||
getComplex().getSubterrainPlanner(),
|
getEngine().getWorld().minHeight(),
|
||||||
getComplex().getSubterrainPlanner() == null ? List.of() : getComplex().getSubterrainPlanner().plansForBounds(chunkBlockX - 1, chunkBlockZ - 1, chunkBlockX + 16, chunkBlockZ + 16)
|
resolverState.complex,
|
||||||
|
subterrainPlanner == null ? null : subterrainPlanner.chunkSampler(x, z)
|
||||||
);
|
);
|
||||||
CarveResolver carveResolver = new CarveResolver(resolutionContext);
|
CarveResolver carveResolver = new CarveResolver(resolutionContext);
|
||||||
terrain.forEachCell(carveResolver::apply);
|
terrain.forEachCell(carveResolver::apply);
|
||||||
if (scratch.customCaveBiomePresent) {
|
if (scratch.customCaveBiomePresent) {
|
||||||
addInternalWallsFromMantle(terrain, walls, columnMasks);
|
addInternalWallsFromMantle(terrain, walls, columnMasks);
|
||||||
} else {
|
} else {
|
||||||
addInternalWallsFromMasks(walls, columnMasks);
|
collectInternalWalls(scratch.wallMasks, columnMasks);
|
||||||
}
|
}
|
||||||
addCrossChunkBoundaryWalls(mantle, terrain, walls, boundaryMasks, x, z, surfaceHeights);
|
addCrossChunkBoundaryWalls(mantle, terrain, walls, boundaryMasks, x, z, surfaceHeights);
|
||||||
getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
|
getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
|
||||||
|
|
||||||
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
||||||
try {
|
try {
|
||||||
IrisData wallData = getData();
|
WallPainter painter = new WallPainter(output, terrain, resolverState, caveBiomeCache, customBiomeCache,
|
||||||
walls.forEach((rx, yy, rz, cavern) -> {
|
surfaceHeights, getData(), chunkBlockX, chunkBlockZ);
|
||||||
HydrologyCaveCell hydrology = terrain.hydrology(rx, yy, rz);
|
if (!scratch.customCaveBiomePresent) {
|
||||||
if (hydrology != null && hydrology.protectsPlacement()
|
paintInternalWalls(painter, scratch.wallMasks, boundaryMasks);
|
||||||
|| subterrainCell(rx + chunkBlockX, yy, rz + chunkBlockZ).owned()) {
|
}
|
||||||
return;
|
walls.forEach(painter);
|
||||||
}
|
|
||||||
int worldX = rx + chunkBlockX;
|
|
||||||
int worldZ = rz + chunkBlockZ;
|
|
||||||
String customBiome = cavern.getCustomBiome();
|
|
||||||
IrisBiome biome = customBiome.isEmpty()
|
|
||||||
? resolveCaveBiome(caveBiomeCache, worldX, yy, worldZ, resolverState)
|
|
||||||
: resolveCustomBiome(customBiomeCache, customBiome);
|
|
||||||
|
|
||||||
if (biome != null) {
|
|
||||||
NativeBlockState data = biome.getWall().get(rng, worldX, yy, worldZ, wallData);
|
|
||||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(rx, rz);
|
|
||||||
|
|
||||||
if (data != null && B.isSolid(output.getRaw(rx, yy, rz)) && yy < surfaceHeights[columnIndex]) {
|
|
||||||
output.setRaw(rx, yy, rz, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||||
processColumnFromMask(
|
processColumnFromMask(
|
||||||
@@ -205,7 +190,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
z,
|
z,
|
||||||
resolverState,
|
resolverState,
|
||||||
caveBiomeCache,
|
caveBiomeCache,
|
||||||
customBiomeCache
|
customBiomeCache,
|
||||||
|
scratch.layers
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +209,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
z,
|
z,
|
||||||
resolverState,
|
resolverState,
|
||||||
caveBiomeCache,
|
caveBiomeCache,
|
||||||
customBiomeCache
|
customBiomeCache,
|
||||||
|
scratch.layers
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,11 +244,6 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SubterrainCell subterrainCell(int x, int y, int z) {
|
|
||||||
SubterrainCell cell = getEngine().getSubterrainCell(x, y, z);
|
|
||||||
return cell == null ? SubterrainCell.OUTSIDE : cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void preserveSubterrain(int blockX, int blockZ, Hunk<NativeBlockState> output) {
|
public void preserveSubterrain(int blockX, int blockZ, Hunk<NativeBlockState> output) {
|
||||||
SubterrainPlanner planner = getComplex().getSubterrainPlanner();
|
SubterrainPlanner planner = getComplex().getSubterrainPlanner();
|
||||||
if (planner == null || planner.isEmpty()) {
|
if (planner == null || planner.isEmpty()) {
|
||||||
@@ -437,8 +419,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int localX = x & 15;
|
int localX = x & 15;
|
||||||
int localZ = z & 15;
|
int localZ = z & 15;
|
||||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||||
SubterrainCell feature = context.subterrainPlanner() == null ? SubterrainCell.OUTSIDE : context.subterrainPlanner().sample(context.subterrainPlans(),
|
SubterrainCell feature = context.subterrainSampler() == null ? SubterrainCell.OUTSIDE
|
||||||
context.chunkBlockX() + localX, y + getEngine().getWorld().minHeight(), context.chunkBlockZ() + localZ);
|
: context.subterrainSampler().sampleCandidates(context.chunkBlockX() + localX, y + context.minimumY(),
|
||||||
|
context.chunkBlockZ() + localZ);
|
||||||
if (feature.owned() && (!feature.solid() || feature.room().reservedSolid())) {
|
if (feature.owned() && (!feature.solid() || feature.room().reservedSolid())) {
|
||||||
context.output().setRaw(localX, y, localZ, SubterrainRasterizer.state(feature));
|
context.output().setRaw(localX, y, localZ, SubterrainRasterizer.state(feature));
|
||||||
if (feature.carve()) {
|
if (feature.carve()) {
|
||||||
@@ -492,7 +475,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
if (isFluidIntent(cavern)) {
|
if (isFluidIntent(cavern)) {
|
||||||
fluid = hydrology == null
|
fluid = hydrology == null
|
||||||
? context.chunkContext().getFluid().get(localX, localZ)
|
? context.chunkContext().getFluid().get(localX, localZ)
|
||||||
: getComplex().resolveHydrologyFluid(
|
: context.complex().resolveHydrologyFluid(
|
||||||
hydrology.fluidProfileKey(),
|
hydrology.fluidProfileKey(),
|
||||||
context.chunkBlockX() + localX,
|
context.chunkBlockX() + localX,
|
||||||
context.chunkBlockZ() + localZ
|
context.chunkBlockZ() + localZ
|
||||||
@@ -526,35 +509,87 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int caveLavaHeight,
|
int caveLavaHeight,
|
||||||
int chunkBlockX,
|
int chunkBlockX,
|
||||||
int chunkBlockZ,
|
int chunkBlockZ,
|
||||||
SubterrainPlanner subterrainPlanner,
|
int minimumY,
|
||||||
List<SubterrainPlan> subterrainPlans
|
IrisComplex complex,
|
||||||
|
SubterrainPlanner.ChunkSampler subterrainSampler
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInternalWallsFromMasks(CarveWallBuffer walls, CarveColumnMask[] columnMasks) {
|
static void collectInternalWalls(CarveColumnMask[] wallMasks, CarveColumnMask[] columnMasks) {
|
||||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||||
CarveColumnMask columnMask = columnMasks[columnIndex];
|
|
||||||
if (columnMask.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rx = columnIndex >> 4;
|
int rx = columnIndex >> 4;
|
||||||
int rz = columnIndex & 15;
|
int rz = columnIndex & 15;
|
||||||
int yy = columnMask.nextSetBit(0);
|
wallMasks[columnIndex].collectWalls(
|
||||||
while (yy >= 0) {
|
columnMasks[columnIndex],
|
||||||
if (rz < 15 && !columnMasks[columnIndex + 1].contains(yy)) {
|
rz > 0 ? columnMasks[columnIndex - 1] : null,
|
||||||
walls.put(rx, yy, rz + 1, BASIC_CAVERN);
|
rz < 15 ? columnMasks[columnIndex + 1] : null,
|
||||||
|
rx > 0 ? columnMasks[columnIndex - 16] : null,
|
||||||
|
rx < 15 ? columnMasks[columnIndex + 16] : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void paintInternalWalls(WallPainter painter, CarveColumnMask[] wallMasks, CarveColumnMask[] boundaryMasks) {
|
||||||
|
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||||
|
CarveColumnMask wallMask = wallMasks[columnIndex];
|
||||||
|
if (wallMask.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int rx = columnIndex >> 4;
|
||||||
|
int rz = columnIndex & 15;
|
||||||
|
CarveColumnMask boundaryMask = boundaryMasks[columnIndex];
|
||||||
|
for (int yy = wallMask.nextSetBit(0); yy >= 0; yy = wallMask.nextSetBit(yy + 1)) {
|
||||||
|
if (!boundaryMask.contains(yy)) {
|
||||||
|
painter.accept(rx, yy, rz, BASIC_CAVERN);
|
||||||
}
|
}
|
||||||
if (rx < 15 && !columnMasks[columnIndex + 16].contains(yy)) {
|
}
|
||||||
walls.put(rx + 1, yy, rz, BASIC_CAVERN);
|
}
|
||||||
}
|
}
|
||||||
if (rz > 0 && !columnMasks[columnIndex - 1].contains(yy)) {
|
|
||||||
walls.put(rx, yy, rz - 1, BASIC_CAVERN);
|
private final class WallPainter implements CarveWallBuffer.Consumer {
|
||||||
}
|
private final Hunk<NativeBlockState> output;
|
||||||
if (rx > 0 && !columnMasks[columnIndex - 16].contains(yy)) {
|
private final CaveTerrainSnapshot terrain;
|
||||||
walls.put(rx - 1, yy, rz, BASIC_CAVERN);
|
private final CaveInputs inputs;
|
||||||
}
|
private final Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache;
|
||||||
yy = columnMask.nextSetBit(yy + 1);
|
private final Map<String, IrisBiome> customBiomeCache;
|
||||||
|
private final int[] surfaceHeights;
|
||||||
|
private final IrisData data;
|
||||||
|
private final int chunkBlockX;
|
||||||
|
private final int chunkBlockZ;
|
||||||
|
|
||||||
|
private WallPainter(Hunk<NativeBlockState> output, CaveTerrainSnapshot terrain, CaveInputs inputs,
|
||||||
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache,
|
||||||
|
int[] surfaceHeights, IrisData data, int chunkBlockX, int chunkBlockZ) {
|
||||||
|
this.output = output;
|
||||||
|
this.terrain = terrain;
|
||||||
|
this.inputs = inputs;
|
||||||
|
this.caveBiomeCache = caveBiomeCache;
|
||||||
|
this.customBiomeCache = customBiomeCache;
|
||||||
|
this.surfaceHeights = surfaceHeights;
|
||||||
|
this.data = data;
|
||||||
|
this.chunkBlockX = chunkBlockX;
|
||||||
|
this.chunkBlockZ = chunkBlockZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(int rx, int yy, int rz, MatterCavern cavern) {
|
||||||
|
HydrologyCaveCell hydrology = terrain.hydrology(rx, yy, rz);
|
||||||
|
int worldX = rx + chunkBlockX;
|
||||||
|
int worldZ = rz + chunkBlockZ;
|
||||||
|
if (hydrology != null && hydrology.protectsPlacement() || inputs.cells.cell(worldX, yy, worldZ).owned()
|
||||||
|
|| yy >= surfaceHeights[PowerOfTwoCoordinates.packLocal16(rx, rz)] || !B.isSolid(output.getRaw(rx, yy, rz))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String customBiome = cavern.getCustomBiome();
|
||||||
|
IrisBiome biome = customBiome.isEmpty()
|
||||||
|
? resolveCaveBiome(caveBiomeCache, worldX, yy, worldZ, inputs)
|
||||||
|
: resolveCustomBiome(customBiomeCache, customBiome);
|
||||||
|
if (biome == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NativeBlockState wall = biome.getWall().get(rng, worldX, yy, worldZ, data);
|
||||||
|
if (wall != null) {
|
||||||
|
output.setRaw(rx, yy, rz, wall);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,10 +656,6 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
int height = maxY + 1;
|
int height = maxY + 1;
|
||||||
MatterCavern[] ownWest = west == null ? null : terrain.composedFace(TerrainMatterView.Face.WEST, height);
|
|
||||||
MatterCavern[] ownEast = east == null ? null : terrain.composedFace(TerrainMatterView.Face.EAST, height);
|
|
||||||
MatterCavern[] ownNorth = north == null ? null : terrain.composedFace(TerrainMatterView.Face.NORTH, height);
|
|
||||||
MatterCavern[] ownSouth = south == null ? null : terrain.composedFace(TerrainMatterView.Face.SOUTH, height);
|
|
||||||
MatterCavern[] fromWest = west == null ? null : TerrainMatterView.getComposedFace(west, TerrainMatterView.Face.EAST, height);
|
MatterCavern[] fromWest = west == null ? null : TerrainMatterView.getComposedFace(west, TerrainMatterView.Face.EAST, height);
|
||||||
MatterCavern[] fromEast = east == null ? null : TerrainMatterView.getComposedFace(east, TerrainMatterView.Face.WEST, height);
|
MatterCavern[] fromEast = east == null ? null : TerrainMatterView.getComposedFace(east, TerrainMatterView.Face.WEST, height);
|
||||||
MatterCavern[] fromNorth = north == null ? null : TerrainMatterView.getComposedFace(north, TerrainMatterView.Face.SOUTH, height);
|
MatterCavern[] fromNorth = north == null ? null : TerrainMatterView.getComposedFace(north, TerrainMatterView.Face.SOUTH, height);
|
||||||
@@ -633,16 +664,16 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
for (int offset = 0; offset < 16; offset++) {
|
for (int offset = 0; offset < 16; offset++) {
|
||||||
int index = yy * 16 + offset;
|
int index = yy * 16 + offset;
|
||||||
if (west != null) {
|
if (west != null) {
|
||||||
tryAddBoundaryWall(ownWest[index], fromWest[index], walls, boundaryMasks, 0, yy, offset);
|
tryAddBoundaryWall(terrain.composedCavern(0, yy, offset), fromWest[index], walls, boundaryMasks, 0, yy, offset);
|
||||||
}
|
}
|
||||||
if (east != null) {
|
if (east != null) {
|
||||||
tryAddBoundaryWall(ownEast[index], fromEast[index], walls, boundaryMasks, 15, yy, offset);
|
tryAddBoundaryWall(terrain.composedCavern(15, yy, offset), fromEast[index], walls, boundaryMasks, 15, yy, offset);
|
||||||
}
|
}
|
||||||
if (north != null) {
|
if (north != null) {
|
||||||
tryAddBoundaryWall(ownNorth[index], fromNorth[index], walls, boundaryMasks, offset, yy, 0);
|
tryAddBoundaryWall(terrain.composedCavern(offset, yy, 0), fromNorth[index], walls, boundaryMasks, offset, yy, 0);
|
||||||
}
|
}
|
||||||
if (south != null) {
|
if (south != null) {
|
||||||
tryAddBoundaryWall(ownSouth[index], fromSouth[index], walls, boundaryMasks, offset, yy, 15);
|
tryAddBoundaryWall(terrain.composedCavern(offset, yy, 15), fromSouth[index], walls, boundaryMasks, offset, yy, 15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -682,7 +713,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int chunkZ,
|
int chunkZ,
|
||||||
CaveInputs resolverState,
|
CaveInputs resolverState,
|
||||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||||
Map<String, IrisBiome> customBiomeCache
|
Map<String, IrisBiome> customBiomeCache,
|
||||||
|
CaveLayerMemo layers
|
||||||
) {
|
) {
|
||||||
if (columnMask == null || columnMask.isEmpty()) {
|
if (columnMask == null || columnMask.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -697,33 +729,19 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int rz = columnIndex & 15;
|
int rz = columnIndex & 15;
|
||||||
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||||
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||||
CaveZone zone = new CaveZone();
|
int height = resolverState.height;
|
||||||
zone.setFloor(firstHeight);
|
layers.reset();
|
||||||
int buf = firstHeight - 1;
|
int floor = firstHeight;
|
||||||
int y = firstHeight;
|
while (floor >= 0 && floor <= height) {
|
||||||
|
int end = columnMask.nextClearBit(floor) - 1;
|
||||||
while (y >= 0) {
|
CaveZone zone = new CaveZone();
|
||||||
if (y <= resolverState.height) {
|
zone.setFloor(floor);
|
||||||
if (y == buf + 1) {
|
zone.setCeiling(Math.min(end, height));
|
||||||
buf = y;
|
if (zone.isValid(height)) {
|
||||||
zone.ceiling = buf;
|
processZone(output, terrain, zone, rx, rz, worldX, worldZ, resolverState,
|
||||||
} else {
|
caveBiomeCache, customBiomeCache, layers);
|
||||||
if (zone.isValid(resolverState.height)) {
|
|
||||||
processZone(output, terrain, zone, rx, rz, worldX, worldZ, resolverState,
|
|
||||||
caveBiomeCache, customBiomeCache);
|
|
||||||
}
|
|
||||||
zone = new CaveZone();
|
|
||||||
zone.setFloor(y);
|
|
||||||
buf = y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
floor = columnMask.nextSetBit(end + 1);
|
||||||
y = columnMask.nextSetBit(y + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zone.isValid(resolverState.height)) {
|
|
||||||
processZone(output, terrain, zone, rx, rz, worldX, worldZ, resolverState,
|
|
||||||
caveBiomeCache, customBiomeCache);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,7 +755,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int chunkZ,
|
int chunkZ,
|
||||||
CaveInputs resolverState,
|
CaveInputs resolverState,
|
||||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||||
Map<String, IrisBiome> customBiomeCache
|
Map<String, IrisBiome> customBiomeCache,
|
||||||
|
CaveLayerMemo layers
|
||||||
) {
|
) {
|
||||||
int firstHeight = boundaryMask.nextSetBit(0);
|
int firstHeight = boundaryMask.nextSetBit(0);
|
||||||
if (firstHeight < 0) {
|
if (firstHeight < 0) {
|
||||||
@@ -748,6 +767,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int rz = columnIndex & 15;
|
int rz = columnIndex & 15;
|
||||||
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||||
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||||
|
layers.reset();
|
||||||
int zoneFloor = firstHeight;
|
int zoneFloor = firstHeight;
|
||||||
int zoneCeiling = firstHeight;
|
int zoneCeiling = firstHeight;
|
||||||
int y = boundaryMask.nextSetBit(firstHeight + 1);
|
int y = boundaryMask.nextSetBit(firstHeight + 1);
|
||||||
@@ -757,7 +777,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
zoneCeiling = y;
|
zoneCeiling = y;
|
||||||
} else {
|
} else {
|
||||||
paintBoundaryZone(output, terrain, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
paintBoundaryZone(output, terrain, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||||
resolverState, caveBiomeCache, customBiomeCache);
|
resolverState, caveBiomeCache, customBiomeCache, layers);
|
||||||
zoneFloor = y;
|
zoneFloor = y;
|
||||||
zoneCeiling = y;
|
zoneCeiling = y;
|
||||||
}
|
}
|
||||||
@@ -765,7 +785,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
paintBoundaryZone(output, terrain, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
paintBoundaryZone(output, terrain, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||||
resolverState, caveBiomeCache, customBiomeCache);
|
resolverState, caveBiomeCache, customBiomeCache, layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void paintBoundaryZone(
|
private void paintBoundaryZone(
|
||||||
@@ -780,7 +800,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int zoneCeiling,
|
int zoneCeiling,
|
||||||
CaveInputs resolverState,
|
CaveInputs resolverState,
|
||||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||||
Map<String, IrisBiome> customBiomeCache
|
Map<String, IrisBiome> customBiomeCache,
|
||||||
|
CaveLayerMemo layers
|
||||||
) {
|
) {
|
||||||
IrisBiome floorBiome = resolveCaveBoundaryBiome(
|
IrisBiome floorBiome = resolveCaveBoundaryBiome(
|
||||||
walls.get(rx, zoneFloor, rz), worldX, zoneFloor, worldZ,
|
walls.get(rx, zoneFloor, rz), worldX, zoneFloor, worldZ,
|
||||||
@@ -794,9 +815,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
|
|
||||||
if (floorBiome != null) {
|
if (floorBiome != null) {
|
||||||
HydrologyCaveCell floorHydrology = terrain.hydrology(rx, zoneFloor, rz);
|
HydrologyCaveCell floorHydrology = terrain.hydrology(rx, zoneFloor, rz);
|
||||||
IrisRiverMaterialConfig bedMaterial = undergroundBedMaterial();
|
IrisRiverMaterialConfig bedMaterial = undergroundBedMaterial(resolverState.dimension);
|
||||||
KList<NativeBlockState> floorLayers = floorBiome.generateLayers(
|
KList<NativeBlockState> floorLayers = floorLayers(layers, floorBiome, worldX, worldZ, zoneFloor, resolverState);
|
||||||
resolverState.dimension, worldX, worldZ, rng, 3, zoneFloor, resolverState.data, resolverState.complex);
|
|
||||||
for (int i = 0; i < zoneFloor - 1; i++) {
|
for (int i = 0; i < zoneFloor - 1; i++) {
|
||||||
if (!floorLayers.hasIndex(i)) {
|
if (!floorLayers.hasIndex(i)) {
|
||||||
break;
|
break;
|
||||||
@@ -833,8 +853,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
|
|
||||||
if (ceilingBiome != null) {
|
if (ceilingBiome != null) {
|
||||||
int worldMaxY = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
int worldMaxY = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
||||||
KList<NativeBlockState> ceilingLayers = ceilingBiome.generateCeilingLayers(
|
KList<NativeBlockState> ceilingLayers = ceilingLayers(layers, ceilingBiome, worldX, worldZ, zoneCeiling, resolverState);
|
||||||
resolverState.dimension, worldX, worldZ, rng, 3, zoneCeiling, resolverState.data, resolverState.complex);
|
|
||||||
for (int i = 0; i < ceilingLayers.size(); i++) {
|
for (int i = 0; i < ceilingLayers.size(); i++) {
|
||||||
int ceilingY = zoneCeiling + i + 1;
|
int ceilingY = zoneCeiling + i + 1;
|
||||||
if (ceilingY >= worldMaxY) {
|
if (ceilingY >= worldMaxY) {
|
||||||
@@ -879,7 +898,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
CaveZone zone, int rx, int rz, int xx, int zz,
|
CaveZone zone, int rx, int rz, int xx, int zz,
|
||||||
CaveInputs resolverState,
|
CaveInputs resolverState,
|
||||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||||
Map<String, IrisBiome> customBiomeCache) {
|
Map<String, IrisBiome> customBiomeCache,
|
||||||
|
CaveLayerMemo layers) {
|
||||||
int maxY = output.getHeight();
|
int maxY = output.getHeight();
|
||||||
|
|
||||||
if (zone.ceiling + 1 < maxY && B.isDecorant(output.getRaw(rx, zone.ceiling + 1, rz))) {
|
if (zone.ceiling + 1 < maxY && B.isDecorant(output.getRaw(rx, zone.ceiling + 1, rz))) {
|
||||||
@@ -899,8 +919,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
|
|
||||||
if (floorBiome != null) {
|
if (floorBiome != null) {
|
||||||
HydrologyCaveCell floorHydrology = terrain.hydrology(rx, zone.floor, rz);
|
HydrologyCaveCell floorHydrology = terrain.hydrology(rx, zone.floor, rz);
|
||||||
IrisRiverMaterialConfig bedMaterial = undergroundBedMaterial();
|
IrisRiverMaterialConfig bedMaterial = undergroundBedMaterial(resolverState.dimension);
|
||||||
KList<NativeBlockState> floorBlocks = floorBiome.generateLayers(resolverState.dimension, xx, zz, rng, 3, zone.floor, resolverState.data, resolverState.complex);
|
KList<NativeBlockState> floorBlocks = floorLayers(layers, floorBiome, xx, zz, zone.floor, resolverState);
|
||||||
for (int i = 0; i < zone.floor - 1; i++) {
|
for (int i = 0; i < zone.floor - 1; i++) {
|
||||||
if (!floorBlocks.hasIndex(i)) {
|
if (!floorBlocks.hasIndex(i)) {
|
||||||
break;
|
break;
|
||||||
@@ -933,7 +953,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ceilingBiome != null) {
|
if (ceilingBiome != null) {
|
||||||
KList<NativeBlockState> ceilingBlocks = ceilingBiome.generateCeilingLayers(resolverState.dimension, xx, zz, rng, 3, zone.ceiling, resolverState.data, resolverState.complex);
|
KList<NativeBlockState> ceilingBlocks = ceilingLayers(layers, ceilingBiome, xx, zz, zone.ceiling, resolverState);
|
||||||
for (int i = 0; i < ceilingBlocks.size(); i++) {
|
for (int i = 0; i < ceilingBlocks.size(); i++) {
|
||||||
int cy = zone.ceiling + i + 1;
|
int cy = zone.ceiling + i + 1;
|
||||||
if (cy >= maxY) {
|
if (cy >= maxY) {
|
||||||
@@ -961,6 +981,44 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
normalizeCaveZoneWaterlogging(output, terrain, zone, rx, rz, xx, zz);
|
normalizeCaveZoneWaterlogging(output, terrain, zone, rx, rz, xx, zz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KList<NativeBlockState> floorLayers(CaveLayerMemo layers, IrisBiome biome, int worldX, int worldZ, int floorY,
|
||||||
|
CaveInputs inputs) {
|
||||||
|
if (!hasHeightFreeFloorLayers(biome)) {
|
||||||
|
return biome.generateLayers(inputs.dimension, worldX, worldZ, rng, 3, floorY, inputs.data, inputs.complex);
|
||||||
|
}
|
||||||
|
KList<NativeBlockState> remembered = layers.floor(biome);
|
||||||
|
if (remembered != null) {
|
||||||
|
return remembered;
|
||||||
|
}
|
||||||
|
KList<NativeBlockState> blocks = biome.generateLayers(inputs.dimension, worldX, worldZ, rng, 3, floorY, inputs.data, inputs.complex);
|
||||||
|
layers.rememberFloor(biome, blocks);
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KList<NativeBlockState> ceilingLayers(CaveLayerMemo layers, IrisBiome biome, int worldX, int worldZ, int ceilingY,
|
||||||
|
CaveInputs inputs) {
|
||||||
|
KList<NativeBlockState> remembered = layers.ceiling(biome);
|
||||||
|
if (remembered != null) {
|
||||||
|
return remembered;
|
||||||
|
}
|
||||||
|
KList<NativeBlockState> blocks = biome.generateCeilingLayers(inputs.dimension, worldX, worldZ, rng, 3, ceilingY, inputs.data, inputs.complex);
|
||||||
|
layers.rememberCeiling(biome, blocks);
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean hasHeightFreeFloorLayers(IrisBiome biome) {
|
||||||
|
if (biome.isLockLayers()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
KList<IrisBiomePaletteLayer> layers = biome.getLayers();
|
||||||
|
for (int index = 0; index < layers.size(); index++) {
|
||||||
|
if (!layers.get(index).getSlopeCondition().isDefault()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void decorateNaturalCaves(int blockX, int blockZ, Hunk<NativeBlockState> output, ChunkContext context) {
|
public void decorateNaturalCaves(int blockX, int blockZ, Hunk<NativeBlockState> output, ChunkContext context) {
|
||||||
Mantle<Matter> mantle = getEngine().getMantle().getMantle();
|
Mantle<Matter> mantle = getEngine().getMantle().getMantle();
|
||||||
CaveTerrainSnapshot terrain = context == null ? null : context.getCaveTerrain();
|
CaveTerrainSnapshot terrain = context == null ? null : context.getCaveTerrain();
|
||||||
@@ -983,26 +1041,30 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int depth = output.getDepth();
|
int depth = output.getDepth();
|
||||||
int height = output.getHeight();
|
int height = output.getHeight();
|
||||||
ColumnExtent extent = output instanceof ColumnExtent columnExtent ? columnExtent : null;
|
ColumnExtent extent = output instanceof ColumnExtent columnExtent ? columnExtent : null;
|
||||||
|
SolidMemo solids = new SolidMemo();
|
||||||
for (int localX = 0; localX < width; localX++) {
|
for (int localX = 0; localX < width; localX++) {
|
||||||
for (int localZ = 0; localZ < depth; localZ++) {
|
for (int localZ = 0; localZ < depth; localZ++) {
|
||||||
int worldX = blockX + localX;
|
int worldX = blockX + localX;
|
||||||
int worldZ = blockZ + localZ;
|
int worldZ = blockZ + localZ;
|
||||||
int floor = -1;
|
int floor = -1;
|
||||||
int top = extent == null ? height - 1 : extent.highestStoredY(localX, localZ);
|
int top = extent == null ? height - 1 : extent.highestStoredY(localX, localZ);
|
||||||
|
boolean previousKnown = false;
|
||||||
|
boolean previousSolid = false;
|
||||||
for (int y = 1; y < height; y++) {
|
for (int y = 1; y < height; y++) {
|
||||||
if (y > top) {
|
if (y > top) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
NativeBlockState state = output.getRaw(localX, y, localZ);
|
boolean solid = solids.test(output.getRaw(localX, y, localZ));
|
||||||
if (B.isSolid(state)) {
|
boolean decorated = false;
|
||||||
|
if (solid) {
|
||||||
if (floor >= 0) {
|
if (floor >= 0) {
|
||||||
CaveZone zone = new CaveZone();
|
CaveZone zone = new CaveZone();
|
||||||
zone.setFloor(floor);
|
zone.setFloor(floor);
|
||||||
zone.setCeiling(y - 1);
|
zone.setCeiling(y - 1);
|
||||||
SubterrainCell floorCell = zone.isValid(resolver.height)
|
SubterrainCell floorCell = zone.isValid(resolver.height)
|
||||||
? subterrainCell(worldX, floor, worldZ) : null;
|
? resolver.cells.cell(worldX, floor, worldZ) : null;
|
||||||
if (floorCell != null && (floorCell.occupied()
|
if (floorCell != null && (floorCell.occupied()
|
||||||
|| !isNaturalTerrainOpening(getComplex(), worldX, floor, worldZ, context))) {
|
|| !isNaturalTerrainOpening(resolver.complex, worldX, floor, worldZ, context))) {
|
||||||
if (caveFloorMarkers && markerRoll(worldX, zone.floor, worldZ, 0xC2B2AE3D27D4EB4FL)) {
|
if (caveFloorMarkers && markerRoll(worldX, zone.floor, worldZ, 0xC2B2AE3D27D4EB4FL)) {
|
||||||
mantle.set(worldX, zone.floor, worldZ, MarkerMatter.CAVE_FLOOR);
|
mantle.set(worldX, zone.floor, worldZ, MarkerMatter.CAVE_FLOOR);
|
||||||
}
|
}
|
||||||
@@ -1012,15 +1074,19 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
worldX, worldZ, resolver, caveBiomes, customBiomes);
|
||||||
decorateZone(output, zone, localX, localZ, worldX, worldZ,
|
decorateZone(output, zone, localX, localZ, worldX, worldZ,
|
||||||
floorBiome, ceilingBiome, floorCell);
|
floorBiome, ceilingBiome, floorCell);
|
||||||
|
decorated = true;
|
||||||
if (extent != null) {
|
if (extent != null) {
|
||||||
top = extent.highestStoredY(localX, localZ);
|
top = extent.highestStoredY(localX, localZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
floor = -1;
|
floor = -1;
|
||||||
}
|
}
|
||||||
} else if (floor < 0 && B.isSolid(output.getRaw(localX, y - 1, localZ))) {
|
} else if (floor < 0 && (previousKnown ? previousSolid
|
||||||
|
: solids.test(output.getRaw(localX, y - 1, localZ)))) {
|
||||||
floor = y;
|
floor = y;
|
||||||
}
|
}
|
||||||
|
previousSolid = solid;
|
||||||
|
previousKnown = !decorated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1057,6 +1123,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
int worldX,
|
int worldX,
|
||||||
int worldZ
|
int worldZ
|
||||||
) {
|
) {
|
||||||
|
if (!terrain.hasHydrology()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
int minimumY = Math.max(0, zone.floor - 1);
|
int minimumY = Math.max(0, zone.floor - 1);
|
||||||
int maximumY = Math.min(output.getHeight() - 1, zone.ceiling + 1);
|
int maximumY = Math.min(output.getHeight() - 1, zone.ceiling + 1);
|
||||||
for (int y = minimumY; y <= maximumY; y++) {
|
for (int y = minimumY; y <= maximumY; y++) {
|
||||||
@@ -1090,7 +1159,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
IrisBiome resolveCaveBoundaryBiome(MatterCavern cavern, int worldX, int y, int worldZ, CaveInputs resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache) {
|
IrisBiome resolveCaveBoundaryBiome(MatterCavern cavern, int worldX, int y, int worldZ, CaveInputs resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache) {
|
||||||
IrisBiome featureBiome = getEngine().getSubterrainBiome(worldX, y, worldZ);
|
IrisBiome featureBiome = resolverState.cells.biome(worldX, y, worldZ);
|
||||||
if (featureBiome != null) {
|
if (featureBiome != null) {
|
||||||
return featureBiome;
|
return featureBiome;
|
||||||
}
|
}
|
||||||
@@ -1127,8 +1196,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The dimension's underground river bed palette, or null when nothing paints. */
|
/** The dimension's underground river bed palette, or null when nothing paints. */
|
||||||
private IrisRiverMaterialConfig undergroundBedMaterial() {
|
private static IrisRiverMaterialConfig undergroundBedMaterial(IrisDimension dimension) {
|
||||||
IrisHydrology hydrology = getDimension().getHydrology();
|
IrisHydrology hydrology = dimension.getHydrology();
|
||||||
if (hydrology == null) {
|
if (hydrology == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1256,6 +1325,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IrisBiome sampleCaveBiome(Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, int x, int y, int z, CaveInputs resolverState) {
|
private IrisBiome sampleCaveBiome(Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, int x, int y, int z, CaveInputs resolverState) {
|
||||||
|
if (resolverState.captures(x, z)) {
|
||||||
|
return resolverState.resolve(x, y, z);
|
||||||
|
}
|
||||||
long key = BlockPosition.toLong(x, y, z);
|
long key = BlockPosition.toLong(x, y, z);
|
||||||
IrisBiome cachedBiome = caveBiomeCache.get(key);
|
IrisBiome cachedBiome = caveBiomeCache.get(key);
|
||||||
if (cachedBiome != null) {
|
if (cachedBiome != null) {
|
||||||
@@ -1287,6 +1359,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
private final int height;
|
private final int height;
|
||||||
private final IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
private final IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
||||||
private final IrisDimensionCarvingResolver.Snapshot snapshot;
|
private final IrisDimensionCarvingResolver.Snapshot snapshot;
|
||||||
|
private final SubterrainChunkCells cells;
|
||||||
|
private final boolean subterrainFirst;
|
||||||
private final int minimumY;
|
private final int minimumY;
|
||||||
private final DimensionStackContext stack;
|
private final DimensionStackContext stack;
|
||||||
private final ProceduralStream<IrisBiome> surfaceBiomes;
|
private final ProceduralStream<IrisBiome> surfaceBiomes;
|
||||||
@@ -1301,6 +1375,10 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
this(engine, false, 0, 0);
|
this(engine, false, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CaveInputs forTerrainChunk(Engine engine, int chunkX, int chunkZ) {
|
||||||
|
return new CaveInputs(engine, false, chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inputs for the content stage of one chunk. A coordinate inside that chunk resolves under the chunk's
|
* Inputs for the content stage of one chunk. A coordinate inside that chunk resolves under the chunk's
|
||||||
* own generation route, exactly the binding the natural terrain stage samples with, so it takes the
|
* own generation route, exactly the binding the natural terrain stage samples with, so it takes the
|
||||||
@@ -1316,14 +1394,19 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
dimension = engine.getDimension();
|
dimension = engine.getDimension();
|
||||||
data = engine.getData();
|
data = engine.getData();
|
||||||
height = engine.getHeight();
|
height = engine.getHeight();
|
||||||
|
cells = SubterrainChunkCells.of(engine, chunkX, chunkZ);
|
||||||
IrisContext context = IrisContext.get();
|
IrisContext context = IrisContext.get();
|
||||||
boolean generating = engine instanceof IrisEngine irisEngine
|
IrisEngine irisEngine = engine instanceof IrisEngine candidate ? candidate : null;
|
||||||
&& irisEngine.hasGenerationRuntimeScope() && !engine.getPlatformHooks().isMainThread()
|
boolean bound = irisEngine != null && irisEngine.hasGenerationRuntimeScope();
|
||||||
|
boolean worker = irisEngine != null && !engine.getPlatformHooks().isMainThread();
|
||||||
|
boolean generating = bound && worker
|
||||||
&& context != null && context.getChunkContext() != null
|
&& context != null && context.getChunkContext() != null
|
||||||
&& context.getChunkContext().getComplex() == complex;
|
&& context.getChunkContext().getComplex() == complex;
|
||||||
boolean natural = generating && context.getChunkContext().isNaturalTerrain();
|
boolean natural = generating && context.getChunkContext().isNaturalTerrain();
|
||||||
boolean scoped = natural || generating && contentChunk;
|
boolean unrouted = !bound && worker && !irisEngine.hasGenerationHistoryRouting();
|
||||||
this.chunkLimited = !natural;
|
boolean scoped = natural || generating && contentChunk || unrouted;
|
||||||
|
this.subterrainFirst = unrouted;
|
||||||
|
this.chunkLimited = !natural && !unrouted;
|
||||||
this.chunkX = chunkX;
|
this.chunkX = chunkX;
|
||||||
this.chunkZ = chunkZ;
|
this.chunkZ = chunkZ;
|
||||||
snapshot = scoped ? IrisDimensionCarvingResolver.snapshot(engine) : null;
|
snapshot = scoped ? IrisDimensionCarvingResolver.snapshot(engine) : null;
|
||||||
@@ -1335,10 +1418,20 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
columns = scoped ? new CaveColumn[1024] : null;
|
columns = scoped ? new CaveColumn[1024] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean captures(int x, int z) {
|
||||||
|
return snapshot != null && (!chunkLimited || (x >> 4) == chunkX && (z >> 4) == chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
IrisBiome resolve(int x, int y, int z) {
|
IrisBiome resolve(int x, int y, int z) {
|
||||||
if (snapshot == null || chunkLimited && ((x >> 4) != chunkX || (z >> 4) != chunkZ)) {
|
if (!captures(x, z)) {
|
||||||
return engine.getCaveBiome(x, y, z, state);
|
return engine.getCaveBiome(x, y, z, state);
|
||||||
}
|
}
|
||||||
|
if (subterrainFirst) {
|
||||||
|
IrisBiome feature = cells.biome(x, y, z);
|
||||||
|
if (feature != null) {
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
}
|
||||||
IrisBiome configured = snapshot.resolveBiome(x, y + minimumY, z);
|
IrisBiome configured = snapshot.resolveBiome(x, y + minimumY, z);
|
||||||
if (configured != null) {
|
if (configured != null) {
|
||||||
return configured;
|
return configured;
|
||||||
@@ -1372,6 +1465,19 @@ public class IrisCarveModifier extends EngineAssignedModifier<NativeBlockState>
|
|||||||
private record CaveColumn(long key, IrisBiome surface, IrisBiome cave, int surfaceY, int minimumDepth) {
|
private record CaveColumn(long key, IrisBiome surface, IrisBiome cave, int surfaceY, int minimumDepth) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class SolidMemo {
|
||||||
|
private NativeBlockState state;
|
||||||
|
private boolean solid;
|
||||||
|
|
||||||
|
private boolean test(NativeBlockState candidate) {
|
||||||
|
if (candidate != state) {
|
||||||
|
state = candidate;
|
||||||
|
solid = B.isSolid(candidate);
|
||||||
|
}
|
||||||
|
return solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class CaveZone {
|
public static class CaveZone {
|
||||||
private int ceiling = -1;
|
private int ceiling = -1;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
package art.arcane.iris.generation.stage;
|
package art.arcane.iris.generation.stage;
|
||||||
|
|
||||||
import art.arcane.iris.generation.biome.IrisBiome;
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
@@ -30,10 +32,12 @@ import java.util.Map;
|
|||||||
final class IrisCarveScratch {
|
final class IrisCarveScratch {
|
||||||
final CarveColumnMask[] columnMasks = new CarveColumnMask[256];
|
final CarveColumnMask[] columnMasks = new CarveColumnMask[256];
|
||||||
final CarveColumnMask[] boundaryMasks = new CarveColumnMask[256];
|
final CarveColumnMask[] boundaryMasks = new CarveColumnMask[256];
|
||||||
|
final CarveColumnMask[] wallMasks = new CarveColumnMask[256];
|
||||||
final int[] surfaceHeights = new int[256];
|
final int[] surfaceHeights = new int[256];
|
||||||
final CarveWallBuffer walls = new CarveWallBuffer(512);
|
final CarveWallBuffer walls = new CarveWallBuffer(512);
|
||||||
final Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
final Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
||||||
final Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = new Long2ObjectOpenHashMap<>(4096);
|
final Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = new Long2ObjectOpenHashMap<>(4096);
|
||||||
|
final CaveLayerMemo layers = new CaveLayerMemo();
|
||||||
int[] upperSurfaceHeights;
|
int[] upperSurfaceHeights;
|
||||||
boolean customCaveBiomePresent;
|
boolean customCaveBiomePresent;
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ final class IrisCarveScratch {
|
|||||||
for (int index = 0; index < columnMasks.length; index++) {
|
for (int index = 0; index < columnMasks.length; index++) {
|
||||||
columnMasks[index] = new CarveColumnMask();
|
columnMasks[index] = new CarveColumnMask();
|
||||||
boundaryMasks[index] = new CarveColumnMask();
|
boundaryMasks[index] = new CarveColumnMask();
|
||||||
|
wallMasks[index] = new CarveColumnMask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +60,7 @@ final class IrisCarveScratch {
|
|||||||
for (int index = 0; index < columnMasks.length; index++) {
|
for (int index = 0; index < columnMasks.length; index++) {
|
||||||
columnMasks[index].clear();
|
columnMasks[index].clear();
|
||||||
boundaryMasks[index].clear();
|
boundaryMasks[index].clear();
|
||||||
|
wallMasks[index].clear();
|
||||||
}
|
}
|
||||||
walls.clear();
|
walls.clear();
|
||||||
customBiomeCache.clear();
|
customBiomeCache.clear();
|
||||||
@@ -63,6 +69,52 @@ final class IrisCarveScratch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class CaveLayerMemo {
|
||||||
|
private static final int SLOTS = 4;
|
||||||
|
|
||||||
|
private final Layers[] floor = new Layers[SLOTS];
|
||||||
|
private final Layers[] ceiling = new Layers[SLOTS];
|
||||||
|
private int floorCount;
|
||||||
|
private int ceilingCount;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
floorCount = 0;
|
||||||
|
ceilingCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<NativeBlockState> floor(IrisBiome biome) {
|
||||||
|
return find(floor, floorCount, biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<NativeBlockState> ceiling(IrisBiome biome) {
|
||||||
|
return find(ceiling, ceilingCount, biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rememberFloor(IrisBiome biome, KList<NativeBlockState> blocks) {
|
||||||
|
if (floorCount < SLOTS) {
|
||||||
|
floor[floorCount++] = new Layers(biome, blocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rememberCeiling(IrisBiome biome, KList<NativeBlockState> blocks) {
|
||||||
|
if (ceilingCount < SLOTS) {
|
||||||
|
ceiling[ceilingCount++] = new Layers(biome, blocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KList<NativeBlockState> find(Layers[] entries, int count, IrisBiome biome) {
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
if (entries[index].biome() == biome) {
|
||||||
|
return entries[index].blocks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Layers(IrisBiome biome, KList<NativeBlockState> blocks) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class CarveColumnMask {
|
final class CarveColumnMask {
|
||||||
private long[] words = new long[8];
|
private long[] words = new long[8];
|
||||||
private int maxWord = -1;
|
private int maxWord = -1;
|
||||||
@@ -108,10 +160,59 @@ final class CarveColumnMask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int nextClearBit(int fromBit) {
|
||||||
|
int startBit = Math.max(0, fromBit);
|
||||||
|
int wordIndex = startBit >> 6;
|
||||||
|
if (wordIndex > maxWord) {
|
||||||
|
return startBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
long word = ~words[wordIndex] & (-1L << (startBit & 63));
|
||||||
|
while (true) {
|
||||||
|
if (word != 0L) {
|
||||||
|
return (wordIndex << 6) + Long.numberOfTrailingZeros(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
wordIndex++;
|
||||||
|
if (wordIndex > maxWord) {
|
||||||
|
return wordIndex << 6;
|
||||||
|
}
|
||||||
|
word = ~words[wordIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean isEmpty() {
|
boolean isEmpty() {
|
||||||
return maxWord < 0;
|
return maxWord < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void collectWalls(CarveColumnMask own, CarveColumnMask first, CarveColumnMask second,
|
||||||
|
CarveColumnMask third, CarveColumnMask fourth) {
|
||||||
|
int top = Math.max(Math.max(topWord(first), topWord(second)), Math.max(topWord(third), topWord(fourth)));
|
||||||
|
clear();
|
||||||
|
if (top < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (top >= words.length) {
|
||||||
|
words = Arrays.copyOf(words, top + 1);
|
||||||
|
}
|
||||||
|
for (int index = 0; index <= top; index++) {
|
||||||
|
long walls = (word(first, index) | word(second, index) | word(third, index) | word(fourth, index))
|
||||||
|
& ~word(own, index);
|
||||||
|
words[index] = walls;
|
||||||
|
if (walls != 0L) {
|
||||||
|
maxWord = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int topWord(CarveColumnMask mask) {
|
||||||
|
return mask == null ? -1 : mask.maxWord;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long word(CarveColumnMask mask, int index) {
|
||||||
|
return mask == null || index > mask.maxWord ? 0L : mask.words[index];
|
||||||
|
}
|
||||||
|
|
||||||
boolean contains(int y) {
|
boolean contains(int y) {
|
||||||
if (y < 0) {
|
if (y < 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package art.arcane.iris.generation.subterrain;
|
package art.arcane.iris.generation.subterrain;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -113,6 +114,10 @@ public final class SubterrainPlanner {
|
|||||||
return sample(candidates(Math.floorDiv(x, 16), Math.floorDiv(z, 16)), x, y, z);
|
return sample(candidates(Math.floorDiv(x, 16), Math.floorDiv(z, 16)), x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChunkSampler chunkSampler(int chunkX, int chunkZ) {
|
||||||
|
return new ChunkSampler(this, chunkX, chunkZ, isEmpty() ? List.of() : candidates(chunkX, chunkZ));
|
||||||
|
}
|
||||||
|
|
||||||
public SubterrainCell sample(List<SubterrainPlan> candidates, int x, int y, int z) {
|
public SubterrainCell sample(List<SubterrainPlan> candidates, int x, int y, int z) {
|
||||||
SubterrainCell selected = raw(candidates, x, y, z);
|
SubterrainCell selected = raw(candidates, x, y, z);
|
||||||
if (!selected.fluid()) {
|
if (!selected.fluid()) {
|
||||||
@@ -238,6 +243,79 @@ public final class SubterrainPlanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class ChunkSampler {
|
||||||
|
private final SubterrainPlanner planner;
|
||||||
|
private final int chunkX;
|
||||||
|
private final int chunkZ;
|
||||||
|
private final List<SubterrainPlan> plans;
|
||||||
|
private final int[] columnMinY;
|
||||||
|
private final int[] columnMaxY;
|
||||||
|
|
||||||
|
private ChunkSampler(SubterrainPlanner planner, int chunkX, int chunkZ, List<SubterrainPlan> plans) {
|
||||||
|
this.planner = planner;
|
||||||
|
this.chunkX = chunkX;
|
||||||
|
this.chunkZ = chunkZ;
|
||||||
|
this.plans = plans;
|
||||||
|
if (plans.isEmpty()) {
|
||||||
|
columnMinY = null;
|
||||||
|
columnMaxY = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
columnMinY = new int[256];
|
||||||
|
columnMaxY = new int[256];
|
||||||
|
Arrays.fill(columnMinY, Integer.MAX_VALUE);
|
||||||
|
Arrays.fill(columnMaxY, Integer.MIN_VALUE);
|
||||||
|
int baseX = chunkX << 4;
|
||||||
|
int baseZ = chunkZ << 4;
|
||||||
|
for (int index = 0; index < plans.size(); index++) {
|
||||||
|
SubterrainBounds bounds = plans.get(index).bounds();
|
||||||
|
int lowX = Math.max(bounds.minX(), baseX) - baseX;
|
||||||
|
int highX = Math.min(bounds.maxX(), baseX + 15) - baseX;
|
||||||
|
int lowZ = Math.max(bounds.minZ(), baseZ) - baseZ;
|
||||||
|
int highZ = Math.min(bounds.maxZ(), baseZ + 15) - baseZ;
|
||||||
|
for (int localX = lowX; localX <= highX; localX++) {
|
||||||
|
for (int localZ = lowZ; localZ <= highZ; localZ++) {
|
||||||
|
int column = (localX << 4) | localZ;
|
||||||
|
columnMinY[column] = Math.min(columnMinY[column], bounds.minY());
|
||||||
|
columnMaxY[column] = Math.max(columnMaxY[column], bounds.maxY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return plans.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubterrainCell sample(int x, int y, int z) {
|
||||||
|
if ((x >> 4) != chunkX || (z >> 4) != chunkZ) {
|
||||||
|
return planner.sample(x, y, z);
|
||||||
|
}
|
||||||
|
if (plans.isEmpty() || y < planner.minY || y >= planner.maxYExclusive) {
|
||||||
|
return SubterrainCell.OUTSIDE;
|
||||||
|
}
|
||||||
|
return sampleColumn(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubterrainCell sampleCandidates(int x, int y, int z) {
|
||||||
|
if (plans.isEmpty()) {
|
||||||
|
return SubterrainCell.OUTSIDE;
|
||||||
|
}
|
||||||
|
if ((x >> 4) != chunkX || (z >> 4) != chunkZ) {
|
||||||
|
return planner.sample(plans, x, y, z);
|
||||||
|
}
|
||||||
|
return sampleColumn(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubterrainCell sampleColumn(int x, int y, int z) {
|
||||||
|
int column = ((x & 15) << 4) | (z & 15);
|
||||||
|
if (y < columnMinY[column] || y > columnMaxY[column]) {
|
||||||
|
return SubterrainCell.OUTSIDE;
|
||||||
|
}
|
||||||
|
return planner.sample(plans, x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private record CandidateTile(int chunkX, int chunkZ, List<SubterrainPlan> plans) {
|
private record CandidateTile(int chunkX, int chunkZ, List<SubterrainPlan> plans) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,20 @@ public class CaveTerrainSnapshotTest {
|
|||||||
for (TerrainMatterView.Face face : TerrainMatterView.Face.values()) {
|
for (TerrainMatterView.Face face : TerrainMatterView.Face.values()) {
|
||||||
for (int height : new int[]{0, 1, 37, SECTIONS * 16, SECTIONS * 16 + 9}) {
|
for (int height : new int[]{0, 1, 37, SECTIONS * 16, SECTIONS * 16 + 9}) {
|
||||||
MatterCavern[] expected = TerrainMatterView.getComposedFace(chunk, face, height);
|
MatterCavern[] expected = TerrainMatterView.getComposedFace(chunk, face, height);
|
||||||
MatterCavern[] actual = snapshot.composedFace(face, height);
|
assertEquals(height * 16, expected.length);
|
||||||
assertEquals(expected.length, actual.length);
|
|
||||||
for (int index = 0; index < expected.length; index++) {
|
for (int index = 0; index < expected.length; index++) {
|
||||||
assertSame(face + " " + index, expected[index], actual[index]);
|
int offset = index & 15;
|
||||||
|
int x = switch (face) {
|
||||||
|
case WEST -> 0;
|
||||||
|
case EAST -> 15;
|
||||||
|
case NORTH, SOUTH -> offset;
|
||||||
|
};
|
||||||
|
int z = switch (face) {
|
||||||
|
case NORTH -> 0;
|
||||||
|
case SOUTH -> 15;
|
||||||
|
case WEST, EAST -> offset;
|
||||||
|
};
|
||||||
|
assertSame(face + " " + index, expected[index], snapshot.composedCavern(x, index >> 4, z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
@@ -9,6 +9,7 @@ import art.arcane.iris.generation.runtime.Engine;
|
|||||||
import art.arcane.iris.generation.runtime.EnginePlatformHooks;
|
import art.arcane.iris.generation.runtime.EnginePlatformHooks;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
|
import art.arcane.iris.generation.runtime.SubterrainChunkCells;
|
||||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
import art.arcane.iris.generation.terrain.IrisDimensionCarvingEntry;
|
import art.arcane.iris.generation.terrain.IrisDimensionCarvingEntry;
|
||||||
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
||||||
@@ -148,6 +149,41 @@ public class IrisCarveModifierCaveInputsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unroutedWorkerCapturesColumnsLikeTheEngineIncludingSubterrainBiomes() {
|
||||||
|
try (Fixture fixture = new Fixture()) {
|
||||||
|
when(fixture.engine.hasGenerationRuntimeScope()).thenReturn(false);
|
||||||
|
when(fixture.engine.hasGenerationHistoryRouting()).thenReturn(false);
|
||||||
|
doReturn(SubterrainChunkCells.delegated(fixture.engine)).when(fixture.engine).subterrainChunkCells(anyInt(), anyInt());
|
||||||
|
IrisBiome feature = biome("feature", 0);
|
||||||
|
doReturn(feature).when(fixture.engine).getSubterrainBiome(-20, 33, 18);
|
||||||
|
doReturn(feature).when(fixture.scalar).getSubterrainBiome(-20, 33, 18);
|
||||||
|
IrisCarveModifier.CaveInputs inputs = IrisCarveModifier.CaveInputs.forContentChunk(fixture.engine, -2, 1);
|
||||||
|
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
||||||
|
for (int x = -35; x <= -13; x++) {
|
||||||
|
for (int z = 13; z <= 35; z += 2) {
|
||||||
|
for (int y : new int[]{0, 30, 33, 34, 39, 40}) {
|
||||||
|
assertSame(x + "," + y + "," + z, fixture.scalar.getCaveBiome(x, y, z, state), inputs.resolve(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSame(feature, inputs.resolve(-20, 33, 18));
|
||||||
|
assertTrue(fixture.samples.get() > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void routedOrBoundUnscopedEnginesKeepCoordinateDispatch() {
|
||||||
|
try (Fixture fixture = new Fixture()) {
|
||||||
|
when(fixture.engine.hasGenerationRuntimeScope()).thenReturn(false);
|
||||||
|
when(fixture.engine.hasGenerationHistoryRouting()).thenReturn(true);
|
||||||
|
IrisBiome historical = biome("historical", 0);
|
||||||
|
when(fixture.engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any(IrisDimensionCarvingResolver.State.class))).thenReturn(historical);
|
||||||
|
assertSame(historical, IrisCarveModifier.CaveInputs.forTerrainChunk(fixture.engine, -2, 1).resolve(-20, 20, 18));
|
||||||
|
assertEquals(0, fixture.samples.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static IrisBiome biome(String key, int depth) {
|
private static IrisBiome biome(String key, int depth) {
|
||||||
IrisBiome biome = new IrisBiome();
|
IrisBiome biome = new IrisBiome();
|
||||||
biome.setLoadKey(key);
|
biome.setLoadKey(key);
|
||||||
|
|||||||
+4
-2
@@ -67,7 +67,8 @@ public class IrisCarveModifierInferenceIsolationTest {
|
|||||||
int.class,
|
int.class,
|
||||||
IrisCarveModifier.CaveInputs.class,
|
IrisCarveModifier.CaveInputs.class,
|
||||||
Long2ObjectOpenHashMap.class,
|
Long2ObjectOpenHashMap.class,
|
||||||
Map.class
|
Map.class,
|
||||||
|
CaveLayerMemo.class
|
||||||
);
|
);
|
||||||
paintBoundaryZone.setAccessible(true);
|
paintBoundaryZone.setAccessible(true);
|
||||||
paintBoundaryZone.invoke(
|
paintBoundaryZone.invoke(
|
||||||
@@ -83,7 +84,8 @@ public class IrisCarveModifierInferenceIsolationTest {
|
|||||||
1,
|
1,
|
||||||
new IrisCarveModifier.CaveInputs(engine),
|
new IrisCarveModifier.CaveInputs(engine),
|
||||||
new Long2ObjectOpenHashMap<IrisBiome>(),
|
new Long2ObjectOpenHashMap<IrisBiome>(),
|
||||||
customBiomes
|
customBiomes,
|
||||||
|
new CaveLayerMemo()
|
||||||
);
|
);
|
||||||
|
|
||||||
IrisBiome fallback = new IrisBiome();
|
IrisBiome fallback = new IrisBiome();
|
||||||
|
|||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
package art.arcane.iris.generation.stage;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiomePaletteLayer;
|
||||||
|
import art.arcane.iris.generation.biome.IrisSurfaceLayerFallback;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisSlopeClip;
|
||||||
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import art.arcane.volmlib.util.noise.CNG;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class IrisCarveModifierLayerMemoTest {
|
||||||
|
private static final Map<Integer, NativeBlockState> BLOCKS = new HashMap<>();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void heightFreeFloorLayersIgnoreTheZoneFloor() {
|
||||||
|
IrisData data = mock(IrisData.class);
|
||||||
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
when(complex.hasTerrain3D()).thenReturn(true);
|
||||||
|
when(complex.terrainSurfaceSlope(anyInt(), anyInt(), anyInt())).thenAnswer(call -> (double) (int) call.getArgument(1));
|
||||||
|
RNG rng = new RNG(31L);
|
||||||
|
IrisBiomePaletteLayer top = layer(data, 2, new IrisSlopeClip());
|
||||||
|
IrisBiomePaletteLayer under = layer(data, 3, new IrisSlopeClip());
|
||||||
|
IrisBiome biome = new IrisBiome().setLayers(new KList<IrisBiomePaletteLayer>().qadd(top).qadd(under));
|
||||||
|
IrisDimension dimension = new IrisDimension().setSurfaceLayerFallback(IrisSurfaceLayerFallback.TOP_LAYER);
|
||||||
|
|
||||||
|
assertTrue(IrisCarveModifier.hasHeightFreeFloorLayers(biome));
|
||||||
|
KList<NativeBlockState> reference = biome.generateLayers(dimension, 14D, -9D, rng, 3, 0, data, complex);
|
||||||
|
for (int floor = -64; floor < 320; floor += 7) {
|
||||||
|
assertEquals(reference, biome.generateLayers(dimension, 14D, -9D, rng, 3, floor, data, complex));
|
||||||
|
}
|
||||||
|
|
||||||
|
biome.setLockLayers(true);
|
||||||
|
assertFalse(IrisCarveModifier.hasHeightFreeFloorLayers(biome));
|
||||||
|
biome.setLockLayers(false);
|
||||||
|
biome.setLayers(new KList<IrisBiomePaletteLayer>().qadd(top).qadd(layer(data, 1, new IrisSlopeClip(0D, 2D))));
|
||||||
|
assertFalse(IrisCarveModifier.hasHeightFreeFloorLayers(biome));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void memoAnswersOnlyTheBiomesOfTheCurrentColumn() {
|
||||||
|
CaveLayerMemo memo = new CaveLayerMemo();
|
||||||
|
IrisBiome first = new IrisBiome();
|
||||||
|
IrisBiome second = new IrisBiome();
|
||||||
|
KList<NativeBlockState> floor = new KList<>();
|
||||||
|
KList<NativeBlockState> ceiling = new KList<>();
|
||||||
|
memo.rememberFloor(first, floor);
|
||||||
|
memo.rememberCeiling(second, ceiling);
|
||||||
|
|
||||||
|
assertSame(floor, memo.floor(first));
|
||||||
|
assertNull(memo.floor(second));
|
||||||
|
assertSame(ceiling, memo.ceiling(second));
|
||||||
|
assertNull(memo.ceiling(first));
|
||||||
|
memo.reset();
|
||||||
|
assertNull(memo.floor(first));
|
||||||
|
assertNull(memo.ceiling(second));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiomePaletteLayer layer(IrisData data, int thickness, IrisSlopeClip slope) {
|
||||||
|
IrisBiomePaletteLayer layer = mock(IrisBiomePaletteLayer.class);
|
||||||
|
CNG height = mock(CNG.class);
|
||||||
|
when(layer.getZoom()).thenReturn(1D);
|
||||||
|
when(layer.getMinHeight()).thenReturn(thickness);
|
||||||
|
when(layer.getMaxHeight()).thenReturn(thickness);
|
||||||
|
when(layer.getSlopeCondition()).thenReturn(slope);
|
||||||
|
when(layer.getHeightGenerator(any(RNG.class), same(data))).thenReturn(height);
|
||||||
|
when(height.fit(anyInt(), anyInt(), anyDouble(), anyDouble())).thenReturn(thickness);
|
||||||
|
when(layer.get(any(RNG.class), anyInt(), anyDouble(), anyDouble(), anyDouble(), same(data)))
|
||||||
|
.thenAnswer(call -> block((int) call.getArgument(1)));
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NativeBlockState block(int signature) {
|
||||||
|
return BLOCKS.computeIfAbsent(signature, key -> mock(NativeBlockState.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-31
@@ -112,39 +112,15 @@ public class IrisCarveModifierZoneParityTest {
|
|||||||
|
|
||||||
private List<String> bitsetZones(CarveColumnMask columnMask, int maxHeight) {
|
private List<String> bitsetZones(CarveColumnMask columnMask, int maxHeight) {
|
||||||
List<String> zones = new ArrayList<>();
|
List<String> zones = new ArrayList<>();
|
||||||
int firstHeight = columnMask.nextSetBit(0);
|
int floor = columnMask.nextSetBit(0);
|
||||||
if (firstHeight < 0) {
|
while (floor >= 0 && floor <= maxHeight) {
|
||||||
return zones;
|
int end = columnMask.nextClearBit(floor) - 1;
|
||||||
}
|
int ceiling = Math.min(end, maxHeight);
|
||||||
|
if (isValidZone(floor, ceiling, maxHeight)) {
|
||||||
int floor = firstHeight;
|
zones.add(zoneKey(floor, ceiling));
|
||||||
int ceiling = -1;
|
|
||||||
int buf = firstHeight - 1;
|
|
||||||
int y = firstHeight;
|
|
||||||
while (y >= 0) {
|
|
||||||
if (y >= 0 && y <= maxHeight) {
|
|
||||||
if (y == buf + 1) {
|
|
||||||
buf = y;
|
|
||||||
ceiling = buf;
|
|
||||||
} else if (isValidZone(floor, ceiling, maxHeight)) {
|
|
||||||
zones.add(zoneKey(floor, ceiling));
|
|
||||||
floor = y;
|
|
||||||
ceiling = -1;
|
|
||||||
buf = y;
|
|
||||||
} else {
|
|
||||||
floor = y;
|
|
||||||
ceiling = -1;
|
|
||||||
buf = y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
floor = columnMask.nextSetBit(end + 1);
|
||||||
y = columnMask.nextSetBit(y + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValidZone(floor, ceiling, maxHeight)) {
|
|
||||||
zones.add(zoneKey(floor, ceiling));
|
|
||||||
}
|
|
||||||
|
|
||||||
return zones;
|
return zones;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import art.arcane.volmlib.util.matter.MatterCavern;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -65,6 +68,54 @@ public class IrisCarveScratchTest {
|
|||||||
assertEquals(0, wallCount[0]);
|
assertEquals(0, wallCount[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maskWallsMatchTheNeighborScanOfCarvedCells() {
|
||||||
|
Random random = new Random(52_117L);
|
||||||
|
for (int round = 0; round < 40; round++) {
|
||||||
|
CarveColumnMask[] columns = new CarveColumnMask[256];
|
||||||
|
CarveColumnMask[] walls = new CarveColumnMask[256];
|
||||||
|
for (int index = 0; index < 256; index++) {
|
||||||
|
columns[index] = new CarveColumnMask();
|
||||||
|
walls[index] = new CarveColumnMask();
|
||||||
|
walls[index].add(random.nextInt(700));
|
||||||
|
}
|
||||||
|
int cells = random.nextInt(4000);
|
||||||
|
int height = 64 + random.nextInt(700);
|
||||||
|
for (int cell = 0; cell < cells; cell++) {
|
||||||
|
columns[random.nextInt(256)].add(1 + random.nextInt(height - 1));
|
||||||
|
}
|
||||||
|
Set<String> expected = new HashSet<>();
|
||||||
|
for (int index = 0; index < 256; index++) {
|
||||||
|
int rx = index >> 4;
|
||||||
|
int rz = index & 15;
|
||||||
|
for (int y = columns[index].nextSetBit(0); y >= 0; y = columns[index].nextSetBit(y + 1)) {
|
||||||
|
if (rz < 15 && !columns[index + 1].contains(y)) {
|
||||||
|
expected.add(key(rx, y, rz + 1));
|
||||||
|
}
|
||||||
|
if (rx < 15 && !columns[index + 16].contains(y)) {
|
||||||
|
expected.add(key(rx + 1, y, rz));
|
||||||
|
}
|
||||||
|
if (rz > 0 && !columns[index - 1].contains(y)) {
|
||||||
|
expected.add(key(rx, y, rz - 1));
|
||||||
|
}
|
||||||
|
if (rx > 0 && !columns[index - 16].contains(y)) {
|
||||||
|
expected.add(key(rx - 1, y, rz));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IrisCarveModifier.collectInternalWalls(walls, columns);
|
||||||
|
Set<String> actual = new HashSet<>();
|
||||||
|
for (int index = 0; index < 256; index++) {
|
||||||
|
for (int y = walls[index].nextSetBit(0); y >= 0; y = walls[index].nextSetBit(y + 1)) {
|
||||||
|
actual.add(key(index >> 4, y, index & 15));
|
||||||
|
assertFalse(columns[index].contains(y));
|
||||||
|
}
|
||||||
|
assertEquals(walls[index].nextSetBit(0) < 0, walls[index].isEmpty());
|
||||||
|
}
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String key(int x, int y, int z) {
|
private static String key(int x, int y, int z) {
|
||||||
return x + ":" + y + ":" + z;
|
return x + ":" + y + ":" + z;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ public class IrisNaturalCaveScanTest {
|
|||||||
chunk = mock(MantleChunk.class);
|
chunk = mock(MantleChunk.class);
|
||||||
doReturn(engine).when(modifier).getEngine();
|
doReturn(engine).when(modifier).getEngine();
|
||||||
doReturn(complex).when(modifier).getComplex();
|
doReturn(complex).when(modifier).getComplex();
|
||||||
|
doReturn(complex).when(engine).getComplex();
|
||||||
doReturn(32).when(engine).getHeight();
|
doReturn(32).when(engine).getHeight();
|
||||||
doReturn(new SeedManager(1337L)).when(engine).getSeedManager();
|
doReturn(new SeedManager(1337L)).when(engine).getSeedManager();
|
||||||
doReturn(engineMantle).when(engine).getMantle();
|
doReturn(engineMantle).when(engine).getMantle();
|
||||||
|
|||||||
@@ -638,6 +638,48 @@ public class SubterrainPlannerTest {
|
|||||||
assertTrue(original.sample(anchor.x(), anchor.y(), anchor.z()).occupied());
|
assertTrue(original.sample(anchor.x(), anchor.y(), anchor.z()).occupied());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void chunkSamplersMatchCoordinateSamplingInsideAndAroundTheirChunk() {
|
||||||
|
for (IrisSubterrainFamily family : IrisSubterrainFamily.values()) {
|
||||||
|
int length = family == IrisSubterrainFamily.TECTONIC_FAULT ? 208 : 64;
|
||||||
|
SubterrainPlanner planner = planner(compactFeature(family).setLength(length).setProbability(0.6));
|
||||||
|
SubterrainPlanner reference = planner(compactFeature(family).setLength(length).setProbability(0.6));
|
||||||
|
SubterrainPlan plan = first(planner);
|
||||||
|
int centerChunkX = plan.centerX() >> 4;
|
||||||
|
int centerChunkZ = plan.centerZ() >> 4;
|
||||||
|
int occupied = 0;
|
||||||
|
int emptyChunks = 0;
|
||||||
|
for (int chunkX = centerChunkX - 3; chunkX <= centerChunkX + 3; chunkX += 2) {
|
||||||
|
for (int chunkZ = centerChunkZ - 3; chunkZ <= centerChunkZ + 3; chunkZ += 2) {
|
||||||
|
SubterrainPlanner.ChunkSampler sampler = planner.chunkSampler(chunkX, chunkZ);
|
||||||
|
List<SubterrainPlan> chunkPlans = reference.plansForBounds((chunkX << 4) - 1, (chunkZ << 4) - 1,
|
||||||
|
(chunkX << 4) + 16, (chunkZ << 4) + 16);
|
||||||
|
if (sampler.isEmpty()) {
|
||||||
|
emptyChunks++;
|
||||||
|
}
|
||||||
|
for (int x = (chunkX << 4) - 3; x < (chunkX << 4) + 19; x++) {
|
||||||
|
for (int z = (chunkZ << 4) - 3; z < (chunkZ << 4) + 19; z++) {
|
||||||
|
for (int y = -66; y <= 99; y++) {
|
||||||
|
SubterrainCell expected = reference.sample(x, y, z);
|
||||||
|
assertEquals(family + " " + x + "," + y + "," + z, expected, sampler.sample(x, y, z));
|
||||||
|
assertEquals(family + " candidates " + x + "," + y + "," + z,
|
||||||
|
reference.sample(chunkPlans, x, y, z), sampler.sampleCandidates(x, y, z));
|
||||||
|
if (expected.occupied()) {
|
||||||
|
occupied++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(family + " occupied", occupied > 100);
|
||||||
|
assertTrue(family + " empty chunks", emptyChunks < 16);
|
||||||
|
}
|
||||||
|
SubterrainPlanner empty = new SubterrainPlanner(new SubterrainPlanner.Options(List.of(), 7123, -64, 97, (x, z) -> "test-region"));
|
||||||
|
assertTrue(empty.chunkSampler(3, -5).isEmpty());
|
||||||
|
assertEquals(SubterrainCell.OUTSIDE, empty.chunkSampler(3, -5).sample(50, 10, -70));
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void duplicateIdentifiersAreRejected() {
|
public void duplicateIdentifiersAreRejected() {
|
||||||
IrisSubterrainFeature feature = feature(IrisSubterrainFamily.CENOTE);
|
IrisSubterrainFeature feature = feature(IrisSubterrainFamily.CENOTE);
|
||||||
|
|||||||
Reference in new issue
Block a user