Stream terrain boundary capture

- boundary columns build runs directly instead of voxel lists, tree sets and hash maps
- palette sorted once per column with a direct comparator, same canonical order
- geometry source reuses the last encoded voxel
- equivalence test against the previous encoding
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 13:43:39 -04:00
1 parent 20e10999dd
commit 2fef055e5c
4 files changed
+363 -46

No files matched your search

@@ -95,6 +95,9 @@ public final class IrisTransitionGeometryActuator extends EngineAssignedComponen
private final int minimumY;
private final ChunkContext context;
private final Map<NativeBlockState, BoundaryColumnGeometry.Voxel> voxels = new IdentityHashMap<>();
private NativeBlockState air;
private NativeBlockState lastState;
private BoundaryColumnGeometry.Voxel lastVoxel;
private GeometrySource(Hunk<NativeBlockState> blocks, Hunk<NativeBiome> biomes, int minimumY, ChunkContext context) {
this.blocks = blocks;
@@ -107,9 +110,16 @@ public final class IrisTransitionGeometryActuator extends EngineAssignedComponen
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
NativeBlockState state = blocks.getRaw(localX, worldY - minimumY, localZ);
if (state == null) {
state = IrisPlatforms.get().registries().air();
if (air == null) {
air = IrisPlatforms.get().registries().air();
}
state = air;
}
return voxels.computeIfAbsent(state, GeometrySource::encode);
if (state != lastState) {
lastVoxel = voxels.computeIfAbsent(state, GeometrySource::encode);
lastState = state;
}
return lastVoxel;
}
@Override
@@ -9,14 +9,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeSet;
public final class BoundaryColumnGeometry implements NativeBlockColumn {
public static final int MAXIMUM_HEIGHT = 65_536;
private static final Comparator<Voxel> VOXEL_ORDER = Comparator.comparing(Voxel::stateKey)
.thenComparing(Voxel::phase)
.thenComparing(Voxel::fluidStateKey)
.thenComparing(Voxel::protectedContent);
private static final Comparator<Voxel> VOXEL_ORDER = BoundaryColumnGeometry::compareVoxels;
private static final Voxel AIR = new Voxel("minecraft:air", Phase.AIR, "", false);
private static final BoundaryColumnGeometry EMPTY = new BoundaryColumnGeometry(0, List.of(), new int[0], new short[0]);
@@ -42,36 +38,24 @@ public final class BoundaryColumnGeometry implements NativeBlockColumn {
if (voxels.size() > MAXIMUM_HEIGHT) {
throw new IllegalArgumentException("Geometry column exceeds maximum height");
}
TreeSet<Voxel> used = new TreeSet<>(VOXEL_ORDER);
Voxel previous = null;
Runs runs = new Runs(voxels.size());
for (Voxel voxel : voxels) {
if (previous == null || !previous.equals(voxel)) {
used.add(voxel);
previous = voxel;
}
runs.add(voxel);
}
List<Voxel> palette = List.copyOf(used);
Map<Voxel, Short> indices = new HashMap<>(palette.size());
for (int index = 0; index < palette.size(); index++) {
if (index > Short.MAX_VALUE) {
throw new IllegalArgumentException("Geometry palette exceeds compact index capacity");
}
indices.put(palette.get(index), (short) index);
return runs.build(minimumY);
}
private static int compareVoxels(Voxel first, Voxel second) {
int order = first.stateKey().compareTo(second.stateKey());
if (order != 0) {
return order;
}
int[] ends = new int[voxels.size()];
short[] values = new short[voxels.size()];
int runCount = 0;
previous = null;
for (int offset = 0; offset < voxels.size(); offset++) {
Voxel voxel = voxels.get(offset);
if (previous == null || !previous.equals(voxel)) {
values[runCount++] = indices.get(voxel);
previous = voxel;
}
ends[runCount - 1] = offset + 1;
order = first.phase().compareTo(second.phase());
if (order != 0) {
return order;
}
return new BoundaryColumnGeometry(minimumY, palette,
Arrays.copyOf(ends, runCount), Arrays.copyOf(values, runCount));
order = first.fluidStateKey().compareTo(second.fluidStateKey());
return order != 0 ? order : Boolean.compare(first.protectedContent(), second.protectedContent());
}
public int minimumY() {
@@ -256,6 +240,90 @@ public final class BoundaryColumnGeometry implements NativeBlockColumn {
}
}
static final class Runs {
private static final int LINEAR_PALETTE_LIMIT = 32;
private final Voxel[] voxels;
private final int[] ends;
private int count;
private int length;
Runs(int capacity) {
voxels = new Voxel[capacity];
ends = new int[capacity];
}
void clear() {
count = 0;
length = 0;
}
void add(Voxel voxel) {
Objects.requireNonNull(voxel, "Geometry voxel");
if (count == 0 || !sameVoxel(voxels[count - 1], voxel)) {
voxels[count++] = voxel;
}
ends[count - 1] = ++length;
}
BoundaryColumnGeometry build(int minimumY) {
Voxel[] distinct = new Voxel[count];
int[] slots = new int[count];
Map<Voxel, Integer> lookup = null;
int distinctCount = 0;
for (int run = 0; run < count; run++) {
Voxel voxel = voxels[run];
int slot = lookup == null ? linearIndex(distinct, distinctCount, voxel) : lookup.getOrDefault(voxel, -1);
if (slot < 0) {
slot = distinctCount;
distinct[distinctCount++] = voxel;
if (lookup != null) {
lookup.put(voxel, slot);
} else if (distinctCount > LINEAR_PALETTE_LIMIT) {
lookup = new HashMap<>();
for (int index = 0; index < distinctCount; index++) {
lookup.put(distinct[index], index);
}
}
}
slots[run] = slot;
}
if (distinctCount > Short.MAX_VALUE + 1) {
throw new IllegalArgumentException("Geometry palette exceeds compact index capacity");
}
Voxel[] palette = Arrays.copyOf(distinct, distinctCount);
Arrays.sort(palette, VOXEL_ORDER);
short[] slotIndices = new short[distinctCount];
if (lookup == null) {
for (int slot = 0; slot < distinctCount; slot++) {
slotIndices[slot] = (short) linearIndex(palette, distinctCount, distinct[slot]);
}
} else {
for (int index = 0; index < distinctCount; index++) {
slotIndices[lookup.get(palette[index])] = (short) index;
}
}
short[] values = new short[count];
for (int run = 0; run < count; run++) {
values[run] = slotIndices[slots[run]];
}
return new BoundaryColumnGeometry(minimumY, Arrays.asList(palette), Arrays.copyOf(ends, count), values);
}
private static int linearIndex(Voxel[] values, int size, Voxel voxel) {
for (int index = 0; index < size; index++) {
if (sameVoxel(values[index], voxel)) {
return index;
}
}
return -1;
}
private static boolean sameVoxel(Voxel first, Voxel second) {
return first == second || first.equals(second);
}
}
public enum Phase {
AIR,
SOLID,
@@ -4,9 +4,7 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
@@ -65,14 +63,15 @@ public final class SavedTerrainChunk {
}
Math.addExact(minimumY, height);
List<TerrainBoundarySignature> columns = new ArrayList<>(256);
BoundaryColumnGeometry.Runs runs = new BoundaryColumnGeometry.Runs(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;
}
List<BoundaryColumnGeometry.Voxel> voxels = new ArrayList<>(height);
Map<String, Short> biomePalette = new LinkedHashMap<>();
runs.clear();
List<String> biomePalette = new ArrayList<>(4);
short[] biomeIndices = new short[height / 4];
int surface = 0;
int floor = 0;
@@ -80,7 +79,7 @@ public final class SavedTerrainChunk {
for (int offset = 0; offset < height; offset++) {
int y = minimumY + offset;
BoundaryColumnGeometry.Voxel voxel = Objects.requireNonNull(source.voxel(x, y, z), "voxel");
voxels.add(voxel);
runs.add(voxel);
if (voxel.phase() != BoundaryColumnGeometry.Phase.AIR) {
surface = offset;
}
@@ -92,15 +91,10 @@ public final class SavedTerrainChunk {
}
if (offset % 4 == 0) {
String biome = Objects.requireNonNull(source.biome(x, y, z), "physical biome");
Short index = biomePalette.get(biome);
if (index == null) {
index = (short) biomePalette.size();
biomePalette.put(biome, index);
}
biomeIndices[offset / 4] = index;
biomeIndices[offset / 4] = paletteIndex(biomePalette, biome);
}
}
BoundaryColumnGeometry geometry = BoundaryColumnGeometry.fromVoxels(minimumY, voxels);
BoundaryColumnGeometry geometry = runs.build(minimumY);
OptionalInt groundSurface = source.groundSurface(x, z);
if (groundSurface.isPresent()) {
floor = geometry.surfaceOffsetNear(groundSurface.getAsInt());
@@ -115,13 +109,24 @@ public final class SavedTerrainChunk {
fluid > floor ? OptionalInt.of(fluid) : OptionalInt.empty(), OptionalInt.empty()),
new TerrainBoundarySignature.Samples(
new TerrainBoundarySignature.VerticalLayout(minimumY, 4, biomeIndices.length),
new TerrainBoundarySignature.BiomeEncoding(List.copyOf(biomePalette.keySet()), biomeIndices)),
new TerrainBoundarySignature.BiomeEncoding(biomePalette, biomeIndices)),
geometry));
}
}
return new SavedTerrainChunk(chunkX, chunkZ, nativeStatus, columns);
}
private static short paletteIndex(List<String> palette, String biome) {
for (int index = 0; index < palette.size(); index++) {
String candidate = palette.get(index);
if (candidate == biome || candidate.equals(biome)) {
return (short) index;
}
}
palette.add(biome);
return (short) (palette.size() - 1);
}
public SavedTerrainChunk boundaryOnly() {
if (!hasColumn(1, 1)) {
return this;