mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
History capture fast paths
- Natural terrain capture scans columns as block state runs - Biome samples reuse the palette index of an identical key - Cave biome capture samples the subterrain planner directly when live - Repeated object markers skip decoding - Saved biome encoding hashes each distinct column once
This commit is contained in:
1 parent
d9c5d00be3
commit
f7f9f5f651
7 files changed
+395
-43
No files matched your search
+33
-6
@@ -120,13 +120,40 @@ public final class IrisTransitionGeometryActuator extends EngineAssignedComponen
|
||||
|
||||
@Override
|
||||
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
|
||||
NativeBlockState state = blocks.getRaw(localX, worldY - minimumY, localZ);
|
||||
if (state == null) {
|
||||
if (air == null) {
|
||||
air = IrisPlatforms.get().registries().air();
|
||||
}
|
||||
state = air;
|
||||
return voxelOf(stateAt(localX, worldY - minimumY, localZ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanColumn(int localX, int localZ, int minimumY, int height, SavedTerrainChunk.VoxelRuns runs) {
|
||||
if (height <= 0) {
|
||||
return;
|
||||
}
|
||||
int base = minimumY - this.minimumY;
|
||||
NativeBlockState runState = stateAt(localX, base, localZ);
|
||||
int start = 0;
|
||||
for (int offset = 1; offset < height; offset++) {
|
||||
NativeBlockState state = stateAt(localX, base + offset, localZ);
|
||||
if (state != runState) {
|
||||
runs.add(start, offset, voxelOf(runState));
|
||||
start = offset;
|
||||
runState = state;
|
||||
}
|
||||
}
|
||||
runs.add(start, height, voxelOf(runState));
|
||||
}
|
||||
|
||||
private NativeBlockState stateAt(int localX, int offset, int localZ) {
|
||||
NativeBlockState state = blocks.getRaw(localX, offset, localZ);
|
||||
if (state != null) {
|
||||
return state;
|
||||
}
|
||||
if (air == null) {
|
||||
air = IrisPlatforms.get().registries().air();
|
||||
}
|
||||
return air;
|
||||
}
|
||||
|
||||
private BoundaryColumnGeometry.Voxel voxelOf(NativeBlockState state) {
|
||||
if (state != lastState) {
|
||||
lastVoxel = voxels.computeIfAbsent(state, GeometrySource::encode);
|
||||
lastState = state;
|
||||
|
||||
@@ -257,11 +257,16 @@ public final class BoundaryColumnGeometry implements NativeBlockColumn {
|
||||
}
|
||||
|
||||
void add(Voxel voxel) {
|
||||
add(voxel, 1);
|
||||
}
|
||||
|
||||
void add(Voxel voxel, int run) {
|
||||
Objects.requireNonNull(voxel, "Geometry voxel");
|
||||
if (count == 0 || !sameVoxel(voxels[count - 1], voxel)) {
|
||||
voxels[count++] = voxel;
|
||||
}
|
||||
ends[count - 1] = ++length;
|
||||
length += run;
|
||||
ends[count - 1] = length;
|
||||
}
|
||||
|
||||
BoundaryColumnGeometry build(int minimumY) {
|
||||
|
||||
@@ -16,6 +16,9 @@ import art.arcane.iris.generation.hydrology.HydrologyColumnSample;
|
||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.subterrain.SubterrainCell;
|
||||
import art.arcane.iris.generation.subterrain.SubterrainPlanner;
|
||||
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
||||
import art.arcane.iris.structure.placement.IrisStructurePlacement;
|
||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||
@@ -170,13 +173,14 @@ public final class GenerationSemanticCapture {
|
||||
cavern == null ? null : cavern.getCustomBiome()
|
||||
);
|
||||
});
|
||||
IrisComplex complex = engine.getComplex();
|
||||
mantle.iterateChunk(chunkX, chunkZ, HydrologyCaveCell.class, (localX, y, localZ, cell) -> {
|
||||
if (!caveSpace.test(localX, y, localZ)) {
|
||||
return;
|
||||
}
|
||||
int blockX = minimumX + localX;
|
||||
int blockZ = minimumZ + localZ;
|
||||
if (!hasFullHydrologyWeight(engine.getComplex(), blockX, blockZ)) {
|
||||
if (!hasFullHydrologyWeight(complex, blockX, blockZ)) {
|
||||
return;
|
||||
}
|
||||
captureCave(
|
||||
@@ -199,12 +203,8 @@ public final class GenerationSemanticCapture {
|
||||
Math.addExact(minimumZ, localZ))));
|
||||
}
|
||||
});
|
||||
mantle.iterateChunk(chunkX, chunkZ, String.class, (localX, y, localZ, marker) -> {
|
||||
StructurePlacementMarker.Decoded decoded = StructurePlacementMarker.decode(marker);
|
||||
if (decoded != null) {
|
||||
semantics.addObject(decoded.objectKey());
|
||||
}
|
||||
});
|
||||
ObjectMarkers objects = new ObjectMarkers(semantics);
|
||||
mantle.iterateChunk(chunkX, chunkZ, String.class, (localX, y, localZ, marker) -> objects.add(marker));
|
||||
}
|
||||
|
||||
private static boolean hasFullHydrologyWeight(IrisComplex complex, int blockX, int blockZ) {
|
||||
@@ -273,10 +273,76 @@ public final class GenerationSemanticCapture {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ObjectMarkers {
|
||||
private final ChunkGenerationSemantics.Builder semantics;
|
||||
private String lastMarker;
|
||||
|
||||
private ObjectMarkers(ChunkGenerationSemantics.Builder semantics) {
|
||||
this.semantics = semantics;
|
||||
}
|
||||
|
||||
private void add(String marker) {
|
||||
if (marker != null && marker.equals(lastMarker)) {
|
||||
return;
|
||||
}
|
||||
StructurePlacementMarker.Decoded decoded = StructurePlacementMarker.decode(marker);
|
||||
if (decoded != null) {
|
||||
semantics.addObject(decoded.objectKey());
|
||||
}
|
||||
lastMarker = marker;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SubterrainBiomes {
|
||||
private final Engine engine;
|
||||
private final boolean live;
|
||||
private final SubterrainPlanner planner;
|
||||
private final int minimumY;
|
||||
|
||||
private SubterrainBiomes(Engine engine, boolean live, SubterrainPlanner planner, int minimumY) {
|
||||
this.engine = engine;
|
||||
this.live = live;
|
||||
this.planner = planner;
|
||||
this.minimumY = minimumY;
|
||||
}
|
||||
|
||||
private static SubterrainBiomes of(Engine engine, int minimumY) {
|
||||
if (engine.getClass() != IrisEngine.class) {
|
||||
return new SubterrainBiomes(engine, false, null, 0);
|
||||
}
|
||||
IrisEngine irisEngine = (IrisEngine) engine;
|
||||
if (!irisEngine.hasGenerationRuntimeScope() && irisEngine.getGenerationHistoryRuntimeRouter().isPresent()) {
|
||||
return new SubterrainBiomes(engine, false, null, 0);
|
||||
}
|
||||
IrisComplex complex = engine.getComplex();
|
||||
IrisDimension dimension = engine.getDimension();
|
||||
SubterrainPlanner planner = complex == null || dimension == null || !dimension.allowsSubterrainFeatures()
|
||||
|| dimension.getSubterrainFeatures() == null || dimension.getSubterrainFeatures().isEmpty()
|
||||
? null
|
||||
: complex.getSubterrainPlanner();
|
||||
return new SubterrainBiomes(engine, true, planner, minimumY);
|
||||
}
|
||||
|
||||
private IrisBiome resolve(int x, int y, int z) {
|
||||
if (!live) {
|
||||
return engine.getSubterrainBiome(x, y, z);
|
||||
}
|
||||
if (planner == null) {
|
||||
return null;
|
||||
}
|
||||
SubterrainCell cell = planner.sample(x, y + minimumY, z);
|
||||
if (!cell.occupied() || cell.room().biome().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return engine.getSubterrainBiome(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CaveBiomes {
|
||||
private final Engine engine;
|
||||
private final boolean scoped;
|
||||
private final CaveFallback fallback;
|
||||
private SubterrainBiomes subterrain;
|
||||
private final IrisDimensionCarvingResolver.State fallbackState = new IrisDimensionCarvingResolver.State();
|
||||
private IrisDimensionCarvingResolver.Snapshot snapshot;
|
||||
private int minimumY;
|
||||
@@ -305,8 +371,9 @@ public final class GenerationSemanticCapture {
|
||||
if (snapshot == null) {
|
||||
snapshot = IrisDimensionCarvingResolver.snapshot(engine);
|
||||
minimumY = engine.getWorld().minHeight();
|
||||
subterrain = SubterrainBiomes.of(engine, minimumY);
|
||||
}
|
||||
IrisBiome featureBiome = engine.getSubterrainBiome(x, y, z);
|
||||
IrisBiome featureBiome = subterrain.resolve(x, y, z);
|
||||
if (featureBiome != null) {
|
||||
return featureBiome;
|
||||
}
|
||||
|
||||
@@ -622,10 +622,16 @@ public final class SavedBiomeStore {
|
||||
private static byte[] encode(SavedBiomeChunk chunk) throws IOException {
|
||||
LinkedHashMap<SavedBiomeChunk.Cell, Integer> palette = new LinkedHashMap<>();
|
||||
LinkedHashMap<SavedBiomeChunk.Column, Integer> columns = new LinkedHashMap<>();
|
||||
byte[] columnIndices = new byte[256];
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
SavedBiomeChunk.Column column = chunk.column(x, z);
|
||||
columns.computeIfAbsent(column, ignored -> columns.size());
|
||||
int known = columns.size();
|
||||
int index = columns.computeIfAbsent(column, ignored -> columns.size());
|
||||
columnIndices[z * 16 + x] = (byte) index;
|
||||
if (index < known) {
|
||||
continue;
|
||||
}
|
||||
palette.computeIfAbsent(column.surface(), ignored -> palette.size());
|
||||
palette.computeIfAbsent(column.caveBase(), ignored -> palette.size());
|
||||
for (SavedBiomeChunk.Span span : column.vertical()) {
|
||||
@@ -660,11 +666,7 @@ public final class SavedBiomeStore {
|
||||
writeUnsigned(output, palette.get(span.cell()));
|
||||
}
|
||||
}
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
output.writeByte(columns.get(chunk.column(x, z)));
|
||||
}
|
||||
}
|
||||
output.write(columnIndices);
|
||||
}
|
||||
if (raw.size() > MAXIMUM_DECODED_BYTES) {
|
||||
throw new IOException("Saved biome chunk exceeds its decoded size limit");
|
||||
|
||||
@@ -63,38 +63,32 @@ public final class SavedTerrainChunk {
|
||||
}
|
||||
Math.addExact(minimumY, height);
|
||||
List<TerrainBoundarySignature> columns = new ArrayList<>(256);
|
||||
BoundaryColumnGeometry.Runs runs = new BoundaryColumnGeometry.Runs(height);
|
||||
ColumnScan scan = new ColumnScan(height);
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
if (boundaryOnly && x != 0 && x != 15 && z != 0 && z != 15) {
|
||||
columns.add(null);
|
||||
continue;
|
||||
}
|
||||
runs.clear();
|
||||
scan.reset();
|
||||
source.scanColumn(x, z, minimumY, height, scan);
|
||||
scan.requireCovered();
|
||||
List<String> biomePalette = new ArrayList<>(4);
|
||||
short[] biomeIndices = new short[height / 4];
|
||||
int surface = 0;
|
||||
int floor = 0;
|
||||
int fluid = -1;
|
||||
for (int offset = 0; offset < height; offset++) {
|
||||
int y = minimumY + offset;
|
||||
BoundaryColumnGeometry.Voxel voxel = Objects.requireNonNull(source.voxel(x, y, z), "voxel");
|
||||
runs.add(voxel);
|
||||
if (voxel.phase() != BoundaryColumnGeometry.Phase.AIR) {
|
||||
surface = offset;
|
||||
}
|
||||
if (voxel.phase() == BoundaryColumnGeometry.Phase.SOLID) {
|
||||
floor = offset;
|
||||
}
|
||||
if (!voxel.fluidStateKey().isEmpty()) {
|
||||
fluid = offset;
|
||||
}
|
||||
if (offset % 4 == 0) {
|
||||
String biome = Objects.requireNonNull(source.biome(x, y, z), "physical biome");
|
||||
biomeIndices[offset / 4] = paletteIndex(biomePalette, biome);
|
||||
String lastBiome = null;
|
||||
short lastIndex = 0;
|
||||
for (int offset = 0; offset < height; offset += 4) {
|
||||
String biome = Objects.requireNonNull(source.biome(x, minimumY + offset, z), "physical biome");
|
||||
if (biome != lastBiome) {
|
||||
lastIndex = paletteIndex(biomePalette, biome);
|
||||
lastBiome = biome;
|
||||
}
|
||||
biomeIndices[offset / 4] = lastIndex;
|
||||
}
|
||||
BoundaryColumnGeometry geometry = runs.build(minimumY);
|
||||
int surface = scan.surface;
|
||||
int floor = scan.floor;
|
||||
int fluid = scan.fluid;
|
||||
BoundaryColumnGeometry geometry = scan.runs.build(minimumY);
|
||||
OptionalInt groundSurface = source.groundSurface(x, z);
|
||||
if (groundSurface.isPresent()) {
|
||||
floor = geometry.surfaceOffsetNear(groundSurface.getAsInt());
|
||||
@@ -209,5 +203,64 @@ public final class SavedTerrainChunk {
|
||||
BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) throws IOException;
|
||||
|
||||
String biome(int localX, int worldY, int localZ) throws IOException;
|
||||
|
||||
default void scanColumn(int localX, int localZ, int minimumY, int height, VoxelRuns runs) throws IOException {
|
||||
for (int offset = 0; offset < height; offset++) {
|
||||
runs.add(offset, offset + 1, voxel(localX, minimumY + offset, localZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface VoxelRuns {
|
||||
void add(int startOffset, int endOffset, BoundaryColumnGeometry.Voxel voxel);
|
||||
}
|
||||
|
||||
private static final class ColumnScan implements VoxelRuns {
|
||||
private final BoundaryColumnGeometry.Runs runs;
|
||||
private final int height;
|
||||
private int covered;
|
||||
private int surface;
|
||||
private int floor;
|
||||
private int fluid;
|
||||
|
||||
private ColumnScan(int height) {
|
||||
this.runs = new BoundaryColumnGeometry.Runs(height);
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
runs.clear();
|
||||
covered = 0;
|
||||
surface = 0;
|
||||
floor = 0;
|
||||
fluid = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int startOffset, int endOffset, BoundaryColumnGeometry.Voxel voxel) {
|
||||
Objects.requireNonNull(voxel, "voxel");
|
||||
if (startOffset != covered || endOffset <= startOffset || endOffset > height) {
|
||||
throw new IllegalArgumentException("Voxel runs must cover the column in order");
|
||||
}
|
||||
runs.add(voxel, endOffset - startOffset);
|
||||
covered = endOffset;
|
||||
int last = endOffset - 1;
|
||||
if (voxel.phase() != BoundaryColumnGeometry.Phase.AIR) {
|
||||
surface = last;
|
||||
}
|
||||
if (voxel.phase() == BoundaryColumnGeometry.Phase.SOLID) {
|
||||
floor = last;
|
||||
}
|
||||
if (!voxel.fluidStateKey().isEmpty()) {
|
||||
fluid = last;
|
||||
}
|
||||
}
|
||||
|
||||
private void requireCovered() {
|
||||
if (covered != height) {
|
||||
throw new IllegalStateException("Voxel runs must cover the whole column");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package art.arcane.iris.world.history;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
public class SavedBiomeStoreEncodingTest {
|
||||
@Test
|
||||
public void encodedRecordsMatchTheReferenceEncoding() throws Exception {
|
||||
Method encode = SavedBiomeStore.class.getDeclaredMethod("encode", SavedBiomeChunk.class);
|
||||
encode.setAccessible(true);
|
||||
Random random = new Random(20261022L);
|
||||
for (int sample = 0; sample < 40; sample++) {
|
||||
SavedBiomeChunk chunk = randomChunk(random, sample);
|
||||
assertArrayEquals("sample " + sample, referenceEncode(chunk), (byte[]) encode.invoke(null, chunk));
|
||||
}
|
||||
}
|
||||
|
||||
private static SavedBiomeChunk randomChunk(Random random, int sample) {
|
||||
long activation = 1L + random.nextInt(9);
|
||||
int minimumY = -64;
|
||||
int height = 384;
|
||||
List<SavedBiomeChunk.Cell> cells = new ArrayList<>();
|
||||
int distinctCells = 1 + random.nextInt(sample % 4 == 0 ? 3 : 24);
|
||||
for (int index = 0; index < distinctCells; index++) {
|
||||
cells.add(random.nextInt(9) == 0
|
||||
? SavedBiomeChunk.Cell.unresolved(activation)
|
||||
: new SavedBiomeChunk.Cell(activation, "biome_" + random.nextInt(12), "region_" + random.nextInt(3)));
|
||||
}
|
||||
List<SavedBiomeChunk.Column> shared = new ArrayList<>();
|
||||
int distinctColumns = 1 + random.nextInt(sample % 3 == 0 ? 4 : 40);
|
||||
for (int index = 0; index < distinctColumns; index++) {
|
||||
shared.add(randomColumn(random, cells, minimumY, height));
|
||||
}
|
||||
SavedBiomeChunk.Builder builder = SavedBiomeChunk.builder(
|
||||
new SavedBiomeChunk.Header(3, -7, activation, minimumY, height));
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
SavedBiomeChunk.Column column = shared.get(random.nextInt(shared.size()));
|
||||
if (random.nextInt(4) == 0) {
|
||||
column = new SavedBiomeChunk.Column(column.surface(), column.caveBase(), new ArrayList<>(column.vertical()));
|
||||
}
|
||||
builder.column(x, z, column);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static SavedBiomeChunk.Column randomColumn(Random random, List<SavedBiomeChunk.Cell> cells,
|
||||
int minimumY, int height) {
|
||||
List<SavedBiomeChunk.Span> spans = new ArrayList<>();
|
||||
int start = minimumY;
|
||||
int end = minimumY + height;
|
||||
while (start < end) {
|
||||
int top = Math.min(end, start + 1 + random.nextInt(96));
|
||||
spans.add(new SavedBiomeChunk.Span(start, top, cells.get(random.nextInt(cells.size()))));
|
||||
start = top;
|
||||
}
|
||||
return new SavedBiomeChunk.Column(cells.get(random.nextInt(cells.size())),
|
||||
cells.get(random.nextInt(cells.size())), spans);
|
||||
}
|
||||
|
||||
private static byte[] referenceEncode(SavedBiomeChunk chunk) throws IOException {
|
||||
LinkedHashMap<SavedBiomeChunk.Cell, Integer> palette = new LinkedHashMap<>();
|
||||
LinkedHashMap<SavedBiomeChunk.Column, Integer> columns = new LinkedHashMap<>();
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
SavedBiomeChunk.Column column = chunk.column(x, z);
|
||||
columns.computeIfAbsent(column, ignored -> columns.size());
|
||||
palette.computeIfAbsent(column.surface(), ignored -> palette.size());
|
||||
palette.computeIfAbsent(column.caveBase(), ignored -> palette.size());
|
||||
for (SavedBiomeChunk.Span span : column.vertical()) {
|
||||
palette.computeIfAbsent(span.cell(), ignored -> palette.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
ByteArrayOutputStream raw = new ByteArrayOutputStream();
|
||||
try (DataOutputStream output = new DataOutputStream(raw)) {
|
||||
output.writeLong(chunk.activationId());
|
||||
output.writeInt(chunk.header().minimumY());
|
||||
output.writeInt(chunk.header().height());
|
||||
output.writeInt(palette.size());
|
||||
for (SavedBiomeChunk.Cell cell : palette.keySet()) {
|
||||
output.writeLong(cell.activationId());
|
||||
output.writeByte(cell.isResolved() ? 0 : 1);
|
||||
if (cell.isResolved()) {
|
||||
output.writeUTF(cell.biomeKey());
|
||||
output.writeUTF(cell.regionKey());
|
||||
}
|
||||
}
|
||||
output.writeInt(columns.size());
|
||||
for (SavedBiomeChunk.Column column : columns.keySet()) {
|
||||
writeUnsigned(output, palette.get(column.surface()));
|
||||
writeUnsigned(output, palette.get(column.caveBase()));
|
||||
writeUnsigned(output, column.vertical().size());
|
||||
for (SavedBiomeChunk.Span span : column.vertical()) {
|
||||
writeUnsigned(output, span.maximumYExclusive() - chunk.header().minimumY());
|
||||
writeUnsigned(output, palette.get(span.cell()));
|
||||
}
|
||||
}
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
output.writeByte(columns.get(chunk.column(x, z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
ByteArrayOutputStream encoded = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(encoded);
|
||||
output.writeInt(chunk.chunkX());
|
||||
output.writeInt(chunk.chunkZ());
|
||||
output.writeInt(raw.size());
|
||||
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
|
||||
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
|
||||
raw.writeTo(compressed);
|
||||
} finally {
|
||||
deflater.end();
|
||||
}
|
||||
return encoded.toByteArray();
|
||||
}
|
||||
|
||||
private static void writeUnsigned(DataOutputStream output, int value) throws IOException {
|
||||
int remaining = value;
|
||||
while ((remaining & ~0x7f) != 0) {
|
||||
output.writeByte((remaining & 0x7f) | 0x80);
|
||||
remaining >>>= 7;
|
||||
}
|
||||
output.writeByte(remaining);
|
||||
}
|
||||
}
|
||||
+58
@@ -56,6 +56,24 @@ public class SavedTerrainCaptureEquivalenceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runScanningSourcesMatchThePerVoxelReference() throws Exception {
|
||||
Random random = new Random(20261022L);
|
||||
for (int sample = 0; sample < 32; sample++) {
|
||||
RandomSource voxels = new RandomSource(random, sample);
|
||||
SavedTerrainChunk.VoxelSource runs = new RunScanningSource(voxels, new Random(sample));
|
||||
for (boolean boundaryOnly : new boolean[]{true, false}) {
|
||||
SavedTerrainChunk expected = referenceCapture(-3, 7, voxels, boundaryOnly);
|
||||
SavedTerrainChunk actual = boundaryOnly
|
||||
? SavedTerrainChunk.captureBoundary(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", runs)
|
||||
: SavedTerrainChunk.capture(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", runs);
|
||||
assertSameTerrain(expected, actual);
|
||||
assertArrayEquals(NativeTerrainReceipt.encode(expected, 1L, "epoch"),
|
||||
NativeTerrainReceipt.encode(actual, 1L, "epoch"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void geometryMatchesTheReferenceOnWidePalettes() {
|
||||
Random random = new Random(7L);
|
||||
@@ -184,6 +202,46 @@ public class SavedTerrainCaptureEquivalenceTest {
|
||||
return new BoundaryColumnGeometry.Voxel(state, phase, fluid, protectedContent);
|
||||
}
|
||||
|
||||
private static final class RunScanningSource implements SavedTerrainChunk.VoxelSource {
|
||||
private final RandomSource source;
|
||||
private final Random splits;
|
||||
|
||||
private RunScanningSource(RandomSource source, Random splits) {
|
||||
this.source = source;
|
||||
this.splits = splits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt groundSurface(int localX, int localZ) {
|
||||
return source.groundSurface(localX, localZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
|
||||
return source.voxel(localX, worldY, localZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String biome(int localX, int worldY, int localZ) {
|
||||
return source.biome(localX, worldY, localZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanColumn(int localX, int localZ, int minimumY, int height, SavedTerrainChunk.VoxelRuns runs) {
|
||||
int start = 0;
|
||||
BoundaryColumnGeometry.Voxel current = source.voxel(localX, minimumY, localZ);
|
||||
for (int offset = 1; offset < height; offset++) {
|
||||
BoundaryColumnGeometry.Voxel next = source.voxel(localX, minimumY + offset, localZ);
|
||||
if (next != current || splits.nextInt(16) == 0) {
|
||||
runs.add(start, offset, current);
|
||||
start = offset;
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
runs.add(start, height, current);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RandomSource implements SavedTerrainChunk.VoxelSource {
|
||||
private final BoundaryColumnGeometry.Voxel[][] voxels = new BoundaryColumnGeometry.Voxel[256][HEIGHT];
|
||||
private final String[][] biomes = new String[256][HEIGHT];
|
||||
|
||||
Reference in new issue
Block a user