mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Merged terrain assembly improvements
- Ceiling layer thickness resolves independently from surface layers - Locked strata repeat correctly above the internal height reference - Cave scratch releases generation references after each pass - Terrain actuators omit redundant setup and checks
This commit is contained in:
@@ -22,24 +22,15 @@ import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Validates biome layer stacks. caveCeilingLayers reuses the height generators built from layers,
|
||||
* so a biome with more ceiling entries than surface entries has no generator for the extras; the
|
||||
* engine skips them, and this validator surfaces the mistake to the author at validate time.
|
||||
*/
|
||||
final class PackBiomeLayerValidator {
|
||||
private static final int DEFAULT_LAYER_COUNT = 1;
|
||||
|
||||
private PackBiomeLayerValidator() {
|
||||
}
|
||||
|
||||
static List<String> validateCeilingLayerCounts(File biomesFolder) {
|
||||
static List<String> validateLayers(File biomesFolder) {
|
||||
List<String> blockingErrors = new ArrayList<>();
|
||||
if (biomesFolder == null || !biomesFolder.isDirectory()) {
|
||||
return blockingErrors;
|
||||
@@ -49,24 +40,13 @@ final class PackBiomeLayerValidator {
|
||||
biomeFiles.sort(Comparator.comparing(File::getPath));
|
||||
for (File biomeFile : biomeFiles) {
|
||||
String biomeKey = PackValidationIo.deriveKey(biomesFolder, biomeFile);
|
||||
JSONObject biome;
|
||||
try {
|
||||
biome = new JSONObject(Files.readString(biomeFile.toPath(), StandardCharsets.UTF_8));
|
||||
} catch (Throwable e) {
|
||||
// Invalid JSON is reported by the graph validators; layer counts have nothing to add.
|
||||
JSONObject biome = PackValidationIo.readJson(biomeFile);
|
||||
if (biome == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer layers = arrayLength(biome, "layers", biomeKey, DEFAULT_LAYER_COUNT, blockingErrors);
|
||||
Integer ceiling = arrayLength(biome, "caveCeilingLayers", biomeKey, 0, blockingErrors);
|
||||
if (layers == null || ceiling == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ceiling > layers) {
|
||||
blockingErrors.add("Biome '" + biomeKey + "' declares " + ceiling + " caveCeilingLayers but only "
|
||||
+ layers + " layers. caveCeilingLayers reuses the layers height generators and must not have more entries.");
|
||||
}
|
||||
validateLayerArray(biome, "layers", biomeKey, blockingErrors);
|
||||
validateLayerArray(biome, "caveCeilingLayers", biomeKey, blockingErrors);
|
||||
}
|
||||
return blockingErrors;
|
||||
}
|
||||
@@ -126,17 +106,15 @@ final class PackBiomeLayerValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private static Integer arrayLength(JSONObject biome, String field, String biomeKey, int defaultCount,
|
||||
private static void validateLayerArray(JSONObject biome, String field, String biomeKey,
|
||||
List<String> blockingErrors) {
|
||||
if (!biome.has(field) || biome.isNull(field)) {
|
||||
return defaultCount;
|
||||
return;
|
||||
}
|
||||
|
||||
JSONArray array = biome.optJSONArray(field);
|
||||
if (array == null) {
|
||||
blockingErrors.add("Biome '" + biomeKey + "' " + field + " must be an array.");
|
||||
return null;
|
||||
}
|
||||
return array.length();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ public final class PackValidator {
|
||||
new File(packFolder, "spawners"), new File(packFolder, "entities")));
|
||||
blockingErrors.addAll(PackSpawnValidator.validateCustomBiomeSpawns(
|
||||
new File(packFolder, "biomes"), PackSpawnValidator::resolveEntitySpawnCategory));
|
||||
blockingErrors.addAll(PackBiomeLayerValidator.validateCeilingLayerCounts(new File(packFolder, "biomes")));
|
||||
blockingErrors.addAll(PackBiomeLayerValidator.validateLayers(new File(packFolder, "biomes")));
|
||||
blockingErrors.addAll(PackBiomeLayerValidator.validateDecoratorPalettes(
|
||||
new File(packFolder, "biomes"), new File(packFolder, "snippet/decorator")));
|
||||
PackStyledRangeDefaultValidator.Validation styledRanges = PackStyledRangeDefaultValidator.validate(packFolder);
|
||||
|
||||
@@ -41,7 +41,6 @@ import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterBiomeInject;
|
||||
import art.arcane.volmlib.util.matter.slices.BiomeInjectMatter;
|
||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import java.util.List;
|
||||
@@ -49,7 +48,6 @@ import java.util.Objects;
|
||||
|
||||
public class IrisBiomeActuator extends EngineAssignedActuator<PlatformBiome> {
|
||||
private final RNG rng;
|
||||
private final ChronoLatch cl = new ChronoLatch(5000);
|
||||
private final KMap<String, ResolvedBiome> resolvedBiomes = new KMap<>();
|
||||
private final KMap<String, ResolvedBiome> resolvedPhysicalBiomes = new KMap<>();
|
||||
|
||||
|
||||
@@ -46,11 +46,7 @@ import art.arcane.iris.util.common.data.B;
|
||||
import lombok.Getter;
|
||||
|
||||
public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBlockState> {
|
||||
private static final PlatformBlockState AIR = B.getState("AIR");
|
||||
private static final PlatformBlockState BEDROCK = B.getState("BEDROCK");
|
||||
private static final PlatformBlockState LAVA = B.getState("LAVA");
|
||||
private static final PlatformBlockState GLASS = B.getState("GLASS");
|
||||
private static final PlatformBlockState CAVE_AIR = B.getState("CAVE_AIR");
|
||||
@Getter
|
||||
private final RNG rng;
|
||||
@Getter
|
||||
@@ -83,11 +79,6 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
|
||||
*/
|
||||
@BlockCoordinates
|
||||
public void terrainSliver(int x, int z, int xf, Hunk<PlatformBlockState> h, ChunkContext context) {
|
||||
terrainSliverOptimized(x, z, xf, h, context);
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
private void terrainSliverOptimized(int x, int z, int xf, Hunk<PlatformBlockState> h, ChunkContext context) {
|
||||
int chunkHeight = h.getHeight();
|
||||
int chunkDepth = h.getDepth();
|
||||
IrisDimension dimension = getDimension();
|
||||
|
||||
+1
-3
@@ -42,9 +42,7 @@ public final class IrisTransitionGeometryActuator extends EngineAssignedComponen
|
||||
return;
|
||||
}
|
||||
GeometrySource source = new GeometrySource(blocks, biomes, getEngine().getMinHeight(), context);
|
||||
if (plan != null && plan.hasTransitionAtChunk(x >> 4, z >> 4)) {
|
||||
blendGeometry(x, z, blocks, context, source, plan);
|
||||
}
|
||||
blendGeometry(x, z, blocks, context, source, plan);
|
||||
}
|
||||
|
||||
public static SavedTerrainChunk capture(int x, int z, Hunk<PlatformBlockState> blocks,
|
||||
|
||||
@@ -22,6 +22,8 @@ import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
final class CaveCarveScratch {
|
||||
final int[] columnMaxY = new int[256];
|
||||
final int[] fluidMaxY = new int[256];
|
||||
@@ -70,4 +72,9 @@ final class CaveCarveScratch {
|
||||
int adaptiveGeometryStep = -1;
|
||||
int adaptiveGeometryAxisCells = -1;
|
||||
boolean fullWeightsInitialized;
|
||||
|
||||
void releaseSections() {
|
||||
Arrays.fill(sectionMatter, null);
|
||||
Arrays.fill(sectionSlices, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,6 +436,7 @@ public class IrisCaveCarver3D {
|
||||
|
||||
return carved;
|
||||
} finally {
|
||||
scratchCache.get().releaseSections();
|
||||
carving.set(nested);
|
||||
if (nested) {
|
||||
scratchCache.set(previousScratch);
|
||||
@@ -2695,10 +2696,6 @@ public class IrisCaveCarver3D {
|
||||
return weight;
|
||||
}
|
||||
|
||||
private double signed(double value) {
|
||||
return (value * 2D) - 1D;
|
||||
}
|
||||
|
||||
private double applySurfaceCeilingFade(
|
||||
CaveCarveScratch scratch,
|
||||
double threshold,
|
||||
|
||||
+60
-33
@@ -19,6 +19,7 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.UpperDimensionContext;
|
||||
import art.arcane.iris.engine.mantle.MatterGenerationPhase;
|
||||
import art.arcane.iris.engine.mantle.ComponentFlag;
|
||||
@@ -36,7 +37,6 @@ import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.iris.util.project.stream.utility.ChunkFillableDoubleStream2D;
|
||||
import art.arcane.iris.util.simd.SimdKernels;
|
||||
import art.arcane.iris.util.simd.SimdSupport;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||
@@ -58,6 +58,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
private static final double MIN_WEIGHT = 0.08D;
|
||||
private static final double THRESHOLD_PENALTY = 0.24D;
|
||||
private static final int MAX_BLENDED_PROFILE_PASSES = 2;
|
||||
private static final int MAX_POOLED_WEIGHT_BUFFERS = 64;
|
||||
private static final int KERNEL_WIDTH = (BLEND_RADIUS * 2) + 1;
|
||||
private static final int KERNEL_SIZE = KERNEL_WIDTH * KERNEL_WIDTH;
|
||||
private static final int[] KERNEL_DX = new int[KERNEL_SIZE];
|
||||
@@ -107,8 +108,25 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
if (!complex.allowsMantleChunkWrite(x, z)) {
|
||||
return;
|
||||
}
|
||||
BlendScratch previous = BLEND_SCRATCH.get();
|
||||
BlendScratch scratch = previous.inUse ? new BlendScratch() : previous;
|
||||
if (scratch != previous) {
|
||||
BLEND_SCRATCH.set(scratch);
|
||||
}
|
||||
scratch.inUse = true;
|
||||
try {
|
||||
generateLayer(writer, x, z, context, scratch);
|
||||
} finally {
|
||||
scratch.release();
|
||||
if (scratch != previous) {
|
||||
BLEND_SCRATCH.set(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateLayer(MantleWriter writer, int x, int z, ChunkContext context, BlendScratch blendScratch) {
|
||||
IrisComplex complex = context.getComplex();
|
||||
IrisDimensionCarvingResolver.State resolverState = new IrisDimensionCarvingResolver.State();
|
||||
BlendScratch blendScratch = BLEND_SCRATCH.get();
|
||||
int[] chunkSurfaceHeights = prepareChunkSurfaceHeights(x, z, context, blendScratch.chunkSurfaceHeights);
|
||||
PrecisionStopwatch resolveStopwatch = PrecisionStopwatch.start();
|
||||
List<WeightedProfile> weightedProfiles = resolveWeightedProfiles(x, z, complex, resolverState);
|
||||
@@ -217,12 +235,12 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
BlendScratch blendScratch = BLEND_SCRATCH.get();
|
||||
IrisCaveProfile[] profileField = blendScratch.profileField;
|
||||
Map<IrisCaveProfile, double[]> columnProfileWeights = blendScratch.columnProfileWeights;
|
||||
IdentityHashMap<IrisCaveProfile, Boolean> activeProfiles = blendScratch.activeProfiles;
|
||||
List<IrisCaveProfile> profileOrder = blendScratch.profileOrder;
|
||||
IrisCaveProfile[] kernelProfiles = blendScratch.kernelProfiles;
|
||||
double[] kernelProfileWeights = blendScratch.kernelProfileWeights;
|
||||
activeProfiles.clear();
|
||||
columnProfileWeights.clear();
|
||||
profileOrder.clear();
|
||||
blendScratch.nextWeightBuffer = 0;
|
||||
fillProfileField(profileField, chunkX, chunkZ, complex, resolverState, blendScratch);
|
||||
|
||||
for (int localX = 0; localX < CHUNK_SIZE; localX++) {
|
||||
@@ -270,12 +288,8 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
|
||||
double[] weights = columnProfileWeights.get(profile);
|
||||
if (weights == null) {
|
||||
weights = new double[CHUNK_AREA];
|
||||
weights = blendScratch.acquireWeights();
|
||||
columnProfileWeights.put(profile, weights);
|
||||
} else if (!activeProfiles.containsKey(profile)) {
|
||||
Arrays.fill(weights, 0D);
|
||||
}
|
||||
if (activeProfiles.put(profile, Boolean.TRUE) == null) {
|
||||
profileOrder.add(profile);
|
||||
}
|
||||
weights[columnIndex] = columnWeight;
|
||||
@@ -316,28 +330,27 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
return weightedProfiles;
|
||||
}
|
||||
|
||||
Map<IrisDimensionCarvingEntry, IrisDimensionCarvingEntry[]> dimensionColumnPlans = blendScratch.dimensionColumnPlans;
|
||||
dimensionColumnPlans.clear();
|
||||
Engine engine = getEngineMantle().getEngine();
|
||||
int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||
int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||
|
||||
for (IrisDimensionCarvingEntry entry : entries) {
|
||||
if (entry == null || !entry.isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IrisBiome rootBiome = IrisDimensionCarvingResolver.resolveEntryBiome(getEngineMantle().getEngine(), entry, resolverState);
|
||||
IrisBiome rootBiome = IrisDimensionCarvingResolver.resolveEntryBiome(engine, entry, resolverState);
|
||||
if (rootBiome == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IrisDimensionCarvingEntry[] columnPlan = dimensionColumnPlans.computeIfAbsent(entry, key -> new IrisDimensionCarvingEntry[CHUNK_AREA]);
|
||||
buildDimensionColumnPlan(columnPlan, chunkX, chunkZ, entry, resolverState);
|
||||
|
||||
Map<IrisCaveProfile, double[]> rootProfileColumnWeights = new IdentityHashMap<>();
|
||||
List<IrisCaveProfile> rootProfileOrder = new ArrayList<>();
|
||||
IrisRange worldYRange = entry.getWorldYRange();
|
||||
for (int columnIndex = 0; columnIndex < CHUNK_AREA; columnIndex++) {
|
||||
IrisDimensionCarvingEntry resolvedEntry = columnPlan[columnIndex];
|
||||
IrisBiome resolvedBiome = IrisDimensionCarvingResolver.resolveEntryBiome(getEngineMantle().getEngine(), resolvedEntry, resolverState);
|
||||
IrisDimensionCarvingEntry resolvedEntry = IrisDimensionCarvingResolver.resolveFromRoot(
|
||||
engine, entry, baseX + (columnIndex >> 4), baseZ + (columnIndex & 15), resolverState);
|
||||
IrisBiome resolvedBiome = IrisDimensionCarvingResolver.resolveEntryBiome(engine, resolvedEntry, resolverState);
|
||||
if (resolvedBiome == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -349,7 +362,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
|
||||
double[] columnWeights = rootProfileColumnWeights.get(profile);
|
||||
if (columnWeights == null) {
|
||||
columnWeights = new double[CHUNK_AREA];
|
||||
columnWeights = blendScratch.acquireWeights();
|
||||
rootProfileColumnWeights.put(profile, columnWeights);
|
||||
rootProfileOrder.add(profile);
|
||||
}
|
||||
@@ -364,19 +377,6 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
return weightedProfiles;
|
||||
}
|
||||
|
||||
private void buildDimensionColumnPlan(IrisDimensionCarvingEntry[] columnPlan, int chunkX, int chunkZ, IrisDimensionCarvingEntry entry, IrisDimensionCarvingResolver.State resolverState) {
|
||||
int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||
int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||
for (int localX = 0; localX < CHUNK_SIZE; localX++) {
|
||||
int worldX = baseX + localX;
|
||||
for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) {
|
||||
int worldZ = baseZ + localZ;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
columnPlan[columnIndex] = IrisDimensionCarvingResolver.resolveFromRoot(getEngineMantle().getEngine(), entry, worldX, worldZ, resolverState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillProfileField(IrisCaveProfile[] profileField, int chunkX, int chunkZ, IrisComplex complex, IrisDimensionCarvingResolver.State resolverState, BlendScratch blendScratch) {
|
||||
int startX = PowerOfTwoCoordinates.chunkToBlock(chunkX) - BLEND_RADIUS;
|
||||
int startZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ) - BLEND_RADIUS;
|
||||
@@ -698,8 +698,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
private final IrisCaveProfile[] kernelProfiles = new IrisCaveProfile[KERNEL_SIZE];
|
||||
private final double[] kernelProfileWeights = new double[KERNEL_SIZE];
|
||||
private final IdentityHashMap<IrisCaveProfile, double[]> columnProfileWeights = new IdentityHashMap<>();
|
||||
private final IdentityHashMap<IrisDimensionCarvingEntry, IrisDimensionCarvingEntry[]> dimensionColumnPlans = new IdentityHashMap<>();
|
||||
private final IdentityHashMap<IrisCaveProfile, Boolean> activeProfiles = new IdentityHashMap<>();
|
||||
private final List<double[]> weightBuffers = new ArrayList<>();
|
||||
private final List<IrisCaveProfile> profileOrder = new ArrayList<>();
|
||||
private final double[] fieldSurfaceHeights = new double[FIELD_SIZE * FIELD_SIZE];
|
||||
private final double[] fieldFluidHeights = new double[FIELD_SIZE * FIELD_SIZE];
|
||||
@@ -710,5 +709,33 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
private final IrisBiome[] fieldCaveBiomes = new IrisBiome[FIELD_SIZE * FIELD_SIZE];
|
||||
private final int[] chunkSurfaceHeights = new int[CHUNK_AREA];
|
||||
private final double[] chunkSurfaceHeightSamples = new double[CHUNK_AREA];
|
||||
private int nextWeightBuffer;
|
||||
private boolean inUse;
|
||||
|
||||
private double[] acquireWeights() {
|
||||
if (nextWeightBuffer < weightBuffers.size()) {
|
||||
double[] weights = weightBuffers.get(nextWeightBuffer++);
|
||||
Arrays.fill(weights, 0D);
|
||||
return weights;
|
||||
}
|
||||
double[] weights = new double[CHUNK_AREA];
|
||||
if (weightBuffers.size() < MAX_POOLED_WEIGHT_BUFFERS) {
|
||||
weightBuffers.add(weights);
|
||||
nextWeightBuffer++;
|
||||
}
|
||||
return weights;
|
||||
}
|
||||
|
||||
private void release() {
|
||||
Arrays.fill(profileField, null);
|
||||
Arrays.fill(kernelProfiles, null);
|
||||
Arrays.fill(fieldRegions, null);
|
||||
Arrays.fill(fieldSurfaceBiomes, null);
|
||||
Arrays.fill(fieldCaveBiomes, null);
|
||||
columnProfileWeights.clear();
|
||||
profileOrder.clear();
|
||||
nextWeightBuffer = 0;
|
||||
inUse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
||||
private final transient AtomicCache<IrisBiome> realCarveBiome = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<IrisBiome>> realChildren = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<CNG>> layerHeightGenerators = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<CNG>> layerCeilingHeightGenerators = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<CNG>> layerSeaHeightGenerators = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
|
||||
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
|
||||
|
||||
@@ -74,7 +74,7 @@ final class IrisBiomeLayerGenerator {
|
||||
IrisBiomePaletteLayer layer = layers.get(i);
|
||||
double zoom = layer.getZoom();
|
||||
CNG hgen = heightGenerators.get(i);
|
||||
double d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
int d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
|
||||
IrisSlopeClip sc = layer.getSlopeCondition();
|
||||
|
||||
@@ -88,17 +88,7 @@ final class IrisBiomeLayerGenerator {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < d; j++) {
|
||||
if (data.size() >= maxDepth) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
data.add(layer.get(random, i + j, (wx + j) / zoom, j, (wz - j) / zoom, rdata));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
appendLayer(data, layer, i, d, wx, wz, random, maxDepth, rdata);
|
||||
|
||||
if (data.size() >= maxDepth) {
|
||||
break;
|
||||
@@ -132,31 +122,20 @@ final class IrisBiomeLayerGenerator {
|
||||
return data;
|
||||
}
|
||||
|
||||
KList<CNG> heightGenerators = getLayerHeightGenerators(biome, random, rdata);
|
||||
// Ceiling layers reuse the surface-layer height generators, so entries beyond layers.size() have no generator; skip them.
|
||||
int usableLayers = Math.min(layerCount, heightGenerators.size());
|
||||
KList<CNG> heightGenerators = getHeightGenerators(
|
||||
biome.getLayerCeilingHeightGenerators(), ceilingLayers, 7235, random, rdata);
|
||||
|
||||
for (int i = 0; i < usableLayers; i++) {
|
||||
for (int i = 0; i < layerCount; i++) {
|
||||
IrisBiomePaletteLayer layer = ceilingLayers.get(i);
|
||||
double zoom = layer.getZoom();
|
||||
CNG hgen = heightGenerators.get(i);
|
||||
double d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
int d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
|
||||
if (d <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < d; j++) {
|
||||
if (data.size() >= maxDepth) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
data.add(layer.get(random, i + j, (wx + j) / zoom, j, (wz - j) / zoom, rdata));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
appendLayer(data, layer, i, d, wx, wz, random, maxDepth, rdata);
|
||||
|
||||
if (data.size() >= maxDepth) {
|
||||
break;
|
||||
@@ -198,7 +177,7 @@ final class IrisBiomeLayerGenerator {
|
||||
IrisBiomePaletteLayer layer = layers.get(i);
|
||||
double zoom = layer.getZoom();
|
||||
CNG hgen = heightGenerators.get(i);
|
||||
double d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
int d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
|
||||
IrisSlopeClip sc = layer.getSlopeCondition();
|
||||
|
||||
@@ -212,13 +191,7 @@ final class IrisBiomeLayerGenerator {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < d; j++) {
|
||||
try {
|
||||
data.add(layer.get(random, i + j, (wx + j) / zoom, j, (wz - j) / zoom, rdata));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
appendLayer(data, layer, i, d, wx, wz, random, Integer.MAX_VALUE, rdata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +200,8 @@ final class IrisBiomeLayerGenerator {
|
||||
}
|
||||
|
||||
for (int i = 0; i < maxDepth; i++) {
|
||||
int offset = (512 - height) - i;
|
||||
int index = offset % data.size();
|
||||
real.add(data.get(Math.max(index, 0)));
|
||||
long offset = 512L - height - i;
|
||||
real.add(data.get(Math.floorMod(offset, data.size())));
|
||||
}
|
||||
|
||||
return real;
|
||||
@@ -264,17 +236,7 @@ final class IrisBiomeLayerGenerator {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < d; j++) {
|
||||
if (data.size() >= maxDepth) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
data.add(layer.get(random, i + j, (wx + j) / zoom, j, (wz - j) / zoom, rdata));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
appendLayer(data, layer, i, d, wx, wz, random, maxDepth, rdata);
|
||||
|
||||
if (data.size() >= maxDepth) {
|
||||
break;
|
||||
@@ -285,7 +247,16 @@ final class IrisBiomeLayerGenerator {
|
||||
}
|
||||
|
||||
static KList<CNG> getLayerHeightGenerators(IrisBiome biome, RNG rng, IrisData rdata) {
|
||||
AtomicCache<KList<CNG>> cache = biome.getLayerHeightGenerators();
|
||||
return getHeightGenerators(biome.getLayerHeightGenerators(), biome.getLayers(), 7235, rng, rdata);
|
||||
}
|
||||
|
||||
static KList<CNG> getLayerSeaHeightGenerators(IrisBiome biome, RNG rng, IrisData data) {
|
||||
return getHeightGenerators(biome.getLayerSeaHeightGenerators(), biome.getSeaLayers(), 7735, rng, data);
|
||||
}
|
||||
|
||||
private static KList<CNG> getHeightGenerators(AtomicCache<KList<CNG>> cache,
|
||||
KList<IrisBiomePaletteLayer> layers,
|
||||
int seedOffset, RNG rng, IrisData data) {
|
||||
KList<CNG> cached = cache.getIfPresent();
|
||||
|
||||
if (cached != null) {
|
||||
@@ -296,36 +267,28 @@ final class IrisBiomeLayerGenerator {
|
||||
{
|
||||
KList<CNG> layerHeightGenerators = new KList<>();
|
||||
|
||||
int m = 7235;
|
||||
int m = seedOffset;
|
||||
|
||||
for (IrisBiomePaletteLayer i : biome.getLayers()) {
|
||||
layerHeightGenerators.add(i.getHeightGenerator(rng.nextParallelRNG((m++) * m * m * m), rdata));
|
||||
for (IrisBiomePaletteLayer layer : layers) {
|
||||
layerHeightGenerators.add(layer.getHeightGenerator(rng.nextParallelRNG((m++) * m * m * m), data));
|
||||
}
|
||||
|
||||
return layerHeightGenerators;
|
||||
});
|
||||
}
|
||||
|
||||
static KList<CNG> getLayerSeaHeightGenerators(IrisBiome biome, RNG rng, IrisData data) {
|
||||
AtomicCache<KList<CNG>> cache = biome.getLayerSeaHeightGenerators();
|
||||
KList<CNG> cached = cache.getIfPresent();
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return cache.aquire(() ->
|
||||
{
|
||||
KList<CNG> layerSeaHeightGenerators = new KList<>();
|
||||
|
||||
int m = 7735;
|
||||
|
||||
for (IrisBiomePaletteLayer i : biome.getSeaLayers()) {
|
||||
layerSeaHeightGenerators.add(i.getHeightGenerator(rng.nextParallelRNG((m++) * m * m * m), data));
|
||||
private static void appendLayer(KList<PlatformBlockState> blocks, IrisBiomePaletteLayer layer,
|
||||
int layerIndex, int thickness, double x, double z,
|
||||
RNG random, int maxDepth, IrisData data) {
|
||||
double zoom = layer.getZoom();
|
||||
for (int offset = 0; offset < thickness && blocks.size() < maxDepth; offset++) {
|
||||
try {
|
||||
blocks.add(layer.get(random, layerIndex + offset,
|
||||
(x + offset) / zoom, offset, (z - offset) / zoom, data));
|
||||
} catch (Throwable error) {
|
||||
IrisLogging.reportError(error);
|
||||
}
|
||||
|
||||
return layerSeaHeightGenerators;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static PlatformBlockState getSurfaceBlock(IrisBiome biome, int x, int z, RNG rng, IrisData idm) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackBiomeLayerValidatorTest {
|
||||
@@ -19,14 +18,12 @@ public class PackBiomeLayerValidatorTest {
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void rejectsMoreCeilingLayersThanLayers() throws Exception {
|
||||
public void acceptsMoreCeilingLayersThanSurfaceLayers() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/nested/cave.json",
|
||||
"{\"layers\":[{}],\"caveCeilingLayers\":[{},{},{}]}");
|
||||
|
||||
assertEquals(List.of(
|
||||
"Biome 'nested/cave' declares 3 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."
|
||||
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
|
||||
assertTrue(PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -35,9 +32,7 @@ public class PackBiomeLayerValidatorTest {
|
||||
write(pack, "biomes/defaulted.json", "{\"name\":\"Defaulted\"}");
|
||||
write(pack, "biomes/implicit.json", "{\"caveCeilingLayers\":[{},{}]}");
|
||||
|
||||
assertEquals(List.of(
|
||||
"Biome 'implicit' declares 2 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."
|
||||
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
|
||||
assertTrue(PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -45,7 +40,7 @@ public class PackBiomeLayerValidatorTest {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/ok.json", "{\"layers\":[{},{},{}],\"caveCeilingLayers\":[{},{}]}");
|
||||
|
||||
assertTrue(PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")).isEmpty());
|
||||
assertTrue(PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -56,11 +51,11 @@ public class PackBiomeLayerValidatorTest {
|
||||
assertEquals(List.of(
|
||||
"Biome 'bad' layers must be an array.",
|
||||
"Biome 'bad' caveCeilingLayers must be an array."
|
||||
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
|
||||
), PackBiomeLayerValidator.validateLayers(new File(pack, "biomes")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ceilingLayerOverflowBlocksFullPackValidation() throws Exception {
|
||||
public void independentCeilingLayersPassFullPackValidation() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"regions\":[\"region\"]}");
|
||||
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
|
||||
@@ -68,9 +63,7 @@ public class PackBiomeLayerValidatorTest {
|
||||
|
||||
PackValidationResult result = PackValidator.validate(pack);
|
||||
|
||||
assertFalse(result.isLoadable());
|
||||
assertTrue(result.getBlockingErrors().contains(
|
||||
"Biome 'biome' declares 2 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."));
|
||||
assertTrue(result.getBlockingErrors().toString(), result.isLoadable());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+42
@@ -35,12 +35,15 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@@ -114,6 +117,45 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
carveForcedAirField.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carvingReleasesSectionReferencesAfterSuccessAndFailure() throws Exception {
|
||||
for (boolean fail : new boolean[]{false, true}) {
|
||||
Engine engine = createEngine(128, 110);
|
||||
IrisCaveProfile profile = createProfile(false, false).setAdaptiveSampling(false)
|
||||
.setAllowFluid(false).setDetailWeight(0D)
|
||||
.setDensityThreshold(new IrisStyledRange(2D, 2D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
|
||||
IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile);
|
||||
WriterCapture capture = createWriterCapture(128);
|
||||
if (fail) {
|
||||
AtomicInteger samples = new AtomicInteger();
|
||||
CNG density = mock(CNG.class);
|
||||
doAnswer(invocation -> {
|
||||
if (samples.incrementAndGet() > 300) {
|
||||
throw new IllegalStateException("Density sample failed");
|
||||
}
|
||||
return -1D;
|
||||
}).when(density).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble());
|
||||
baseDensityField.set(carver, density);
|
||||
assertThrows(IllegalStateException.class, () -> carver.carve(capture.writer, 0, 0,
|
||||
fullWeights(), 0D, 0D, null, filledHeights(110)));
|
||||
} else {
|
||||
carver.carve(capture.writer, 0, 0, fullWeights(), 0D, 0D, null, filledHeights(110));
|
||||
}
|
||||
assertFalse(capture.carvedCells.isEmpty());
|
||||
Field scratchField = IrisCaveCarver3D.class.getDeclaredField("scratchCache");
|
||||
scratchField.setAccessible(true);
|
||||
ThreadLocal<?> scratchCache = (ThreadLocal<?>) scratchField.get(carver);
|
||||
CaveCarveScratch scratch = (CaveCarveScratch) scratchCache.get();
|
||||
assertTrue(scratch.sectionMatter.length > 0);
|
||||
for (Matter matter : scratch.sectionMatter) {
|
||||
assertNull(matter);
|
||||
}
|
||||
for (MatterSlice<?> slice : scratch.sectionSlices) {
|
||||
assertNull(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parallelDensityPreservesExactAndAdaptiveCavernsAndLiquids() throws Exception {
|
||||
try (ForkJoinPool pool = new ForkJoinPool(4)) {
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineMetrics;
|
||||
import art.arcane.iris.engine.mantle.EngineMantle;
|
||||
import art.arcane.iris.engine.mantle.MantleWriter;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisCaveProfile;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MantleCarvingComponentScratchTest {
|
||||
private MantleCarvingComponent component;
|
||||
private IrisDimension dimension;
|
||||
private ChunkContext context;
|
||||
private MantleWriter writer;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
public void prepare() {
|
||||
Engine engine = mock(Engine.class);
|
||||
EngineMantle mantle = mock(EngineMantle.class);
|
||||
when(mantle.getEngine()).thenReturn(engine);
|
||||
component = new MantleCarvingComponent(mantle);
|
||||
dimension = new IrisDimension();
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
when(engine.getWorld()).thenReturn(mock(IrisWorld.class));
|
||||
when(engine.getMetrics()).thenReturn(new EngineMetrics(16));
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
when(complex.allowsMantleChunkWrite(anyInt(), anyInt())).thenReturn(true);
|
||||
ProceduralStream<Double> heights = mock(ProceduralStream.class);
|
||||
when(heights.getDouble(anyDouble(), anyDouble())).thenAnswer(invocation -> {
|
||||
double x = invocation.getArgument(0);
|
||||
return x < 0D ? 90D : 70D;
|
||||
});
|
||||
when(complex.getNaturalHeightStream()).thenReturn(heights);
|
||||
ProceduralStream<IrisRegion> regions = mock(ProceduralStream.class);
|
||||
ProceduralStream<IrisBiome> biomes = mock(ProceduralStream.class);
|
||||
ProceduralStream<PlatformBlockState> fluids = mock(ProceduralStream.class);
|
||||
when(regions.get(anyDouble(), anyDouble())).thenReturn(new IrisRegion());
|
||||
when(biomes.get(anyDouble(), anyDouble())).thenReturn(new IrisBiome());
|
||||
when(complex.getRegionStream()).thenReturn(regions);
|
||||
when(complex.getNaturalTrueBiomeStream()).thenReturn(biomes);
|
||||
when(complex.getCaveBiomeStream()).thenReturn(biomes);
|
||||
when(complex.getFluidStream()).thenReturn(fluids);
|
||||
context = mock(ChunkContext.class);
|
||||
when(context.getComplex()).thenReturn(complex);
|
||||
writer = mock(MantleWriter.class);
|
||||
Mantle<Matter> storage = mock(Mantle.class);
|
||||
when(storage.getWorldHeight()).thenReturn(128);
|
||||
when(writer.getMantle()).thenReturn(storage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedGenerationPreservesOuterColumnHeights() throws Exception {
|
||||
IrisCaveCarver3D carver = installProfile();
|
||||
AtomicBoolean entered = new AtomicBoolean();
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
int chunkX = invocation.getArgument(1);
|
||||
int[] heights = invocation.getArgument(7);
|
||||
int expectedHeight = chunkX < 0 ? 90 : 70;
|
||||
assertEquals(expectedHeight, heights[0]);
|
||||
calls.incrementAndGet();
|
||||
if (entered.compareAndSet(false, true)) {
|
||||
component.generateLayer(writer, -3, -3, context);
|
||||
}
|
||||
for (int height : heights) {
|
||||
assertEquals(expectedHeight, height);
|
||||
}
|
||||
return 0;
|
||||
}).when(carver).carve(any(), anyInt(), anyInt(), any(), anyDouble(), anyDouble(),
|
||||
any(), any(), any(), any(), any());
|
||||
|
||||
component.generateLayer(writer, 2, 2, context);
|
||||
|
||||
assertEquals(2, calls.get());
|
||||
assertReleased();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generationReleasesConfigurationReferencesWhenCarvingFails() throws Exception {
|
||||
IrisCaveCarver3D carver = installProfile();
|
||||
doAnswer(invocation -> {
|
||||
throw new IllegalStateException("Carving failed");
|
||||
}).when(carver).carve(any(), anyInt(), anyInt(), any(), anyDouble(), anyDouble(),
|
||||
any(), any(), any(), any(), any());
|
||||
assertThrows(IllegalStateException.class, () -> component.generateLayer(writer, 2, 2, context));
|
||||
assertReleased();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changedProfilesReuseNumericBuffersWithoutKeepingProfileKeys() throws Exception {
|
||||
Object firstBuffer = null;
|
||||
for (int index = 0; index < 4; index++) {
|
||||
installProfile();
|
||||
component.generateLayer(writer, 2, 2, context);
|
||||
Object scratch = assertReleased();
|
||||
List<?> buffers = (List<?>) field(scratch, "weightBuffers");
|
||||
assertEquals(1, buffers.size());
|
||||
if (firstBuffer == null) {
|
||||
firstBuffer = buffers.get(0);
|
||||
} else {
|
||||
assertSame(firstBuffer, buffers.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IrisCaveCarver3D installProfile() throws Exception {
|
||||
IrisCaveProfile profile = new IrisCaveProfile().setEnabled(true);
|
||||
dimension.setCaveProfile(profile);
|
||||
IrisCaveCarver3D carver = mock(IrisCaveCarver3D.class);
|
||||
Map<IrisCaveProfile, IrisCaveCarver3D> carvers = new IdentityHashMap<>();
|
||||
carvers.put(profile, carver);
|
||||
Field cache = MantleCarvingComponent.class.getDeclaredField("profileCarvers");
|
||||
cache.setAccessible(true);
|
||||
cache.set(component, carvers);
|
||||
return carver;
|
||||
}
|
||||
|
||||
private Object assertReleased() throws Exception {
|
||||
Field scratchField = MantleCarvingComponent.class.getDeclaredField("BLEND_SCRATCH");
|
||||
scratchField.setAccessible(true);
|
||||
ThreadLocal<?> scratchCache = (ThreadLocal<?>) scratchField.get(null);
|
||||
Object scratch = scratchCache.get();
|
||||
for (String name : List.of("profileField", "kernelProfiles", "fieldRegions", "fieldSurfaceBiomes", "fieldCaveBiomes")) {
|
||||
for (Object value : (Object[]) field(scratch, name)) {
|
||||
assertNull(value);
|
||||
}
|
||||
}
|
||||
assertEquals(0, ((Map<?, ?>) field(scratch, "columnProfileWeights")).size());
|
||||
assertEquals(0, ((List<?>) field(scratch, "profileOrder")).size());
|
||||
assertFalse((boolean) field(scratch, "inUse"));
|
||||
return scratch;
|
||||
}
|
||||
|
||||
private Object field(Object object, String name) throws Exception {
|
||||
Field field = object.getClass().getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field.get(object);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisBiomeCeilingLayerGuardTest {
|
||||
private static IrisBiome biome(int layers, int ceilingLayers) {
|
||||
IrisBiome biome = new IrisBiome();
|
||||
KList<IrisBiomePaletteLayer> surface = new KList<>();
|
||||
for (int i = 0; i < layers; i++) {
|
||||
surface.add(new IrisBiomePaletteLayer().zero());
|
||||
}
|
||||
KList<IrisBiomePaletteLayer> ceiling = new KList<>();
|
||||
for (int i = 0; i < ceilingLayers; i++) {
|
||||
IrisBiomePaletteLayer layer = new IrisBiomePaletteLayer().zero();
|
||||
layer.setMinHeight(0);
|
||||
layer.setMaxHeight(0);
|
||||
ceiling.add(layer);
|
||||
}
|
||||
biome.setLayers(surface);
|
||||
biome.setCaveCeilingLayers(ceiling);
|
||||
return biome;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void moreCeilingLayersThanLayersMustNotThrow() {
|
||||
IrisBiome biome = biome(1, 3);
|
||||
|
||||
KList<PlatformBlockState> result = biome.generateCeilingLayers(
|
||||
new IrisDimension(), 0, 0, new RNG(1), 8, 64, mock(IrisData.class), null);
|
||||
|
||||
assertTrue("extra ceiling layers are skipped, not crashed on", result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inRangeCeilingLayersKeepWorking() {
|
||||
IrisBiome biome = biome(3, 2);
|
||||
|
||||
KList<PlatformBlockState> result = biome.generateCeilingLayers(
|
||||
new IrisDimension(), 0, 0, new RNG(1), 8, 64, mock(IrisData.class), null);
|
||||
|
||||
assertTrue("zeroed palettes with zero heights produce no blocks", result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void omittedCeilingLayersLeaveCavesUnchanged() {
|
||||
assertTrue(new IrisBiome().getCaveCeilingLayers().isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisBiomeCeilingLayerTest {
|
||||
@Test
|
||||
public void emitsEveryCeilingLayerWithoutSurfaceLayers() {
|
||||
PlatformBlockState first = mock(PlatformBlockState.class);
|
||||
PlatformBlockState second = mock(PlatformBlockState.class);
|
||||
PlatformBlockState third = mock(PlatformBlockState.class);
|
||||
IrisBiome biome = new IrisBiome().setLayers(new KList<>()).setCaveCeilingLayers(
|
||||
new KList<>(layer(first, 2), layer(second, 1), layer(third, 2)));
|
||||
|
||||
KList<PlatformBlockState> result = biome.generateCeilingLayers(
|
||||
new IrisDimension(), 13, -27, new RNG(1), 8, 64, null, null);
|
||||
|
||||
assertEquals(List.of(first, first, second, third, third), result);
|
||||
assertNull(biome.getLayerHeightGenerators().getIfPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ceilingThicknessDoesNotDependOnSurfaceDefinitionOrWarmOrder() {
|
||||
IrisBiome first = variableBiome(1, 1);
|
||||
IrisBiome second = variableBiome(7, 31);
|
||||
RNG rng = new RNG(37);
|
||||
second.getLayerHeightGenerators(rng, null);
|
||||
for (int x = -200; x <= 200; x += 7) {
|
||||
KList<PlatformBlockState> firstCeiling = first.generateCeilingLayers(
|
||||
new IrisDimension(), x, -x, rng, 100, 120, null, null);
|
||||
KList<PlatformBlockState> secondCeiling = second.generateCeilingLayers(
|
||||
new IrisDimension(), x, -x, rng, 100, 120, null, null);
|
||||
assertEquals(firstCeiling.size(), secondCeiling.size());
|
||||
}
|
||||
first.getLayerHeightGenerators(rng, null);
|
||||
assertNotSame(first.getLayerHeightGenerators().getIfPresent().get(0),
|
||||
first.getLayerCeilingHeightGenerators().getIfPresent().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void capsCeilingLayersAtAvailableDepth() {
|
||||
PlatformBlockState first = mock(PlatformBlockState.class);
|
||||
PlatformBlockState second = mock(PlatformBlockState.class);
|
||||
IrisBiome biome = new IrisBiome().setCaveCeilingLayers(new KList<>(layer(first, 2), layer(second, 3)));
|
||||
assertEquals(List.of(first, first, second), biome.generateCeilingLayers(
|
||||
new IrisDimension(), 0, 0, new RNG(1), 3, 64, null, null));
|
||||
assertTrue(biome.generateCeilingLayers(
|
||||
new IrisDimension(), 0, 0, new RNG(1), 0, 64, null, null).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void omittedCeilingLayersLeaveCavesUnchanged() {
|
||||
assertTrue(new IrisBiome().getCaveCeilingLayers().isEmpty());
|
||||
}
|
||||
|
||||
static IrisBiomePaletteLayer layer(PlatformBlockState block, int thickness) {
|
||||
IrisBiomePaletteLayer layer = new IrisBiomePaletteLayer().setMinHeight(thickness).setMaxHeight(thickness);
|
||||
layer.getBlockData().aquire(() -> new KList<>(block));
|
||||
return layer;
|
||||
}
|
||||
|
||||
private static IrisBiome variableBiome(int surfaceMinimum, int surfaceMaximum) {
|
||||
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||
return new IrisBiome()
|
||||
.setLayers(new KList<>(layer(block, surfaceMinimum).setMaxHeight(surfaceMaximum)))
|
||||
.setCaveCeilingLayers(new KList<>(layer(block, 1).setMaxHeight(9)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisBiomeLockedLayerTest {
|
||||
@Test
|
||||
public void repeatsLockedLayersAcrossTheReferenceHeight() {
|
||||
PlatformBlockState first = mock(PlatformBlockState.class);
|
||||
PlatformBlockState second = mock(PlatformBlockState.class);
|
||||
PlatformBlockState third = mock(PlatformBlockState.class);
|
||||
IrisBiome biome = new IrisBiome().setLockLayers(true).setLockLayersMax(8).setLayers(new KList<>(
|
||||
IrisBiomeCeilingLayerTest.layer(first, 1),
|
||||
IrisBiomeCeilingLayerTest.layer(second, 1),
|
||||
IrisBiomeCeilingLayerTest.layer(third, 1)));
|
||||
List<PlatformBlockState> cycle = List.of(first, second, third);
|
||||
int[] heights = {1, 509, 512, 513, 1000, Integer.MAX_VALUE};
|
||||
int[][] expectedCycles = {{1, 0, 2}, {0, 2, 1}, {0, 2, 1}, {2, 1, 0}, {1, 0, 2}, {1, 0, 2}};
|
||||
for (int index = 0; index < heights.length; index++) {
|
||||
KList<PlatformBlockState> blocks = biome.generateLockedLayers(
|
||||
13, -27, new RNG(37), 8, heights[index], null, null);
|
||||
assertEquals(8, blocks.size());
|
||||
for (int depth = 0; depth < blocks.size(); depth++) {
|
||||
assertEquals(cycle.get(expectedCycles[index][depth % 3]), blocks.get(depth));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user