mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
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:
+12
-2
@@ -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;
|
||||
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
package art.arcane.iris.world.history;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.Random;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class SavedTerrainCaptureEquivalenceTest {
|
||||
private static final int MINIMUM_Y = -64;
|
||||
private static final int HEIGHT = 384;
|
||||
private static final Comparator<BoundaryColumnGeometry.Voxel> VOXEL_ORDER = Comparator
|
||||
.comparing(BoundaryColumnGeometry.Voxel::stateKey)
|
||||
.thenComparing(BoundaryColumnGeometry.Voxel::phase)
|
||||
.thenComparing(BoundaryColumnGeometry.Voxel::fluidStateKey)
|
||||
.thenComparing(BoundaryColumnGeometry.Voxel::protectedContent);
|
||||
private static final List<BoundaryColumnGeometry.Voxel> STATES = List.of(
|
||||
voxel("minecraft:stone", BoundaryColumnGeometry.Phase.SOLID, "", false),
|
||||
voxel("minecraft:deepslate", BoundaryColumnGeometry.Phase.SOLID, "", false),
|
||||
voxel("minecraft:air", BoundaryColumnGeometry.Phase.AIR, "", false),
|
||||
voxel("minecraft:cave_air", BoundaryColumnGeometry.Phase.AIR, "", false),
|
||||
voxel("minecraft:water[level=0]", BoundaryColumnGeometry.Phase.FLUID, "minecraft:water[level=0]", false),
|
||||
voxel("minecraft:lava[level=0]", BoundaryColumnGeometry.Phase.FLUID, "minecraft:lava[level=0]", false),
|
||||
voxel("minecraft:oak_log[axis=y]", BoundaryColumnGeometry.Phase.SOLID, "", true),
|
||||
voxel("minecraft:kelp", BoundaryColumnGeometry.Phase.SOLID, "minecraft:water[level=0]", true),
|
||||
voxel("minecraft:stone", BoundaryColumnGeometry.Phase.SOLID, "", true));
|
||||
private static final List<String> BIOMES = List.of("minecraft:plains", "minecraft:river",
|
||||
"minecraft:lush_caves", "minecraft:deep_dark", "minecraft:dripstone_caves");
|
||||
|
||||
@Test
|
||||
public void boundaryAndFullCapturesMatchTheReferenceEncoding() throws Exception {
|
||||
Random random = new Random(20260928L);
|
||||
for (int sample = 0; sample < 48; sample++) {
|
||||
RandomSource source = new RandomSource(random, sample);
|
||||
for (boolean boundaryOnly : new boolean[]{true, false}) {
|
||||
SavedTerrainChunk expected = referenceCapture(-3, 7, source, boundaryOnly);
|
||||
SavedTerrainChunk actual = boundaryOnly
|
||||
? SavedTerrainChunk.captureBoundary(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", source)
|
||||
: SavedTerrainChunk.capture(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", source);
|
||||
assertSameTerrain(expected, actual);
|
||||
assertArrayEquals(NativeTerrainReceipt.encode(expected, 1L, "epoch"),
|
||||
NativeTerrainReceipt.encode(actual, 1L, "epoch"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void geometryMatchesTheReferenceOnWidePalettes() {
|
||||
Random random = new Random(7L);
|
||||
for (int sample = 0; sample < 32; sample++) {
|
||||
ArrayList<BoundaryColumnGeometry.Voxel> voxels = new ArrayList<>(HEIGHT);
|
||||
int distinct = 1 + random.nextInt(sample < 16 ? 12 : 200);
|
||||
while (voxels.size() < HEIGHT) {
|
||||
int state = random.nextInt(distinct);
|
||||
BoundaryColumnGeometry.Voxel voxel = voxel("minecraft:block_" + state,
|
||||
BoundaryColumnGeometry.Phase.values()[state % 2], "", state % 3 == 0);
|
||||
int end = Math.min(HEIGHT, voxels.size() + 1 + random.nextInt(4));
|
||||
while (voxels.size() < end) {
|
||||
voxels.add(voxel);
|
||||
}
|
||||
}
|
||||
assertEquals(referenceGeometry(MINIMUM_Y, voxels), BoundaryColumnGeometry.fromVoxels(MINIMUM_Y, voxels));
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertSameTerrain(SavedTerrainChunk expected, SavedTerrainChunk actual) {
|
||||
assertEquals(expected.chunkX(), actual.chunkX());
|
||||
assertEquals(expected.chunkZ(), actual.chunkZ());
|
||||
assertEquals(expected.nativeStatus(), actual.nativeStatus());
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
assertEquals(expected.hasColumn(x, z), actual.hasColumn(x, z));
|
||||
if (!expected.hasColumn(x, z)) {
|
||||
continue;
|
||||
}
|
||||
TerrainBoundarySignature left = expected.column(-48 + x, 112 + z);
|
||||
TerrainBoundarySignature right = actual.column(-48 + x, 112 + z);
|
||||
assertEquals(left.column(), right.column());
|
||||
assertEquals(left.samples(), right.samples());
|
||||
assertEquals(left.geometry(), right.geometry());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SavedTerrainChunk referenceCapture(int chunkX, int chunkZ, SavedTerrainChunk.VoxelSource source,
|
||||
boolean boundaryOnly) throws IOException {
|
||||
List<TerrainBoundarySignature> columns = new ArrayList<>(256);
|
||||
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<>();
|
||||
short[] biomeIndices = new short[HEIGHT / 4];
|
||||
int surface = 0;
|
||||
int floor = 0;
|
||||
int fluid = -1;
|
||||
for (int offset = 0; offset < HEIGHT; offset++) {
|
||||
int y = MINIMUM_Y + offset;
|
||||
BoundaryColumnGeometry.Voxel voxel = Objects.requireNonNull(source.voxel(x, y, z), "voxel");
|
||||
voxels.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");
|
||||
Short index = biomePalette.get(biome);
|
||||
if (index == null) {
|
||||
index = (short) biomePalette.size();
|
||||
biomePalette.put(biome, index);
|
||||
}
|
||||
biomeIndices[offset / 4] = index;
|
||||
}
|
||||
}
|
||||
BoundaryColumnGeometry geometry = referenceGeometry(MINIMUM_Y, voxels);
|
||||
OptionalInt groundSurface = source.groundSurface(x, z);
|
||||
if (groundSurface.isPresent()) {
|
||||
floor = geometry.surfaceOffsetNear(groundSurface.getAsInt());
|
||||
surface = Math.max(floor, fluid);
|
||||
}
|
||||
columns.add(new TerrainBoundarySignature(
|
||||
new TerrainBoundarySignature.Column(chunkX * 16 + x, chunkZ * 16 + z, surface, floor,
|
||||
fluid > floor ? OptionalInt.of(fluid) : OptionalInt.empty(), OptionalInt.empty()),
|
||||
new TerrainBoundarySignature.Samples(
|
||||
new TerrainBoundarySignature.VerticalLayout(MINIMUM_Y, 4, biomeIndices.length),
|
||||
new TerrainBoundarySignature.BiomeEncoding(List.copyOf(biomePalette.keySet()), biomeIndices)),
|
||||
geometry));
|
||||
}
|
||||
}
|
||||
return new SavedTerrainChunk(chunkX, chunkZ, "minecraft:noise", columns);
|
||||
}
|
||||
|
||||
private static BoundaryColumnGeometry referenceGeometry(int minimumY, List<BoundaryColumnGeometry.Voxel> voxels) {
|
||||
TreeSet<BoundaryColumnGeometry.Voxel> used = new TreeSet<>(VOXEL_ORDER);
|
||||
BoundaryColumnGeometry.Voxel previous = null;
|
||||
for (BoundaryColumnGeometry.Voxel voxel : voxels) {
|
||||
if (previous == null || !previous.equals(voxel)) {
|
||||
used.add(voxel);
|
||||
previous = voxel;
|
||||
}
|
||||
}
|
||||
List<BoundaryColumnGeometry.Voxel> palette = List.copyOf(used);
|
||||
Map<BoundaryColumnGeometry.Voxel, Short> indices = new HashMap<>(palette.size());
|
||||
for (int index = 0; index < palette.size(); index++) {
|
||||
indices.put(palette.get(index), (short) index);
|
||||
}
|
||||
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++) {
|
||||
BoundaryColumnGeometry.Voxel voxel = voxels.get(offset);
|
||||
if (previous == null || !previous.equals(voxel)) {
|
||||
values[runCount++] = indices.get(voxel);
|
||||
previous = voxel;
|
||||
}
|
||||
ends[runCount - 1] = offset + 1;
|
||||
}
|
||||
return new BoundaryColumnGeometry(minimumY, palette, Arrays.copyOf(ends, runCount), Arrays.copyOf(values, runCount));
|
||||
}
|
||||
|
||||
private static BoundaryColumnGeometry.Voxel voxel(String state, BoundaryColumnGeometry.Phase phase, String fluid,
|
||||
boolean protectedContent) {
|
||||
return new BoundaryColumnGeometry.Voxel(state, phase, fluid, protectedContent);
|
||||
}
|
||||
|
||||
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];
|
||||
private final int[] ground = new int[256];
|
||||
|
||||
private RandomSource(Random random, int sample) {
|
||||
for (int column = 0; column < 256; column++) {
|
||||
int offset = 0;
|
||||
while (offset < HEIGHT) {
|
||||
BoundaryColumnGeometry.Voxel state = STATES.get(random.nextInt(STATES.size()));
|
||||
BoundaryColumnGeometry.Voxel copy = random.nextBoolean() ? state
|
||||
: new BoundaryColumnGeometry.Voxel(new String(state.stateKey()), state.phase(),
|
||||
new String(state.fluidStateKey()), state.protectedContent());
|
||||
int end = Math.min(HEIGHT, offset + 1 + random.nextInt(sample % 3 == 0 ? 3 : 64));
|
||||
while (offset < end) {
|
||||
voxels[column][offset++] = copy;
|
||||
}
|
||||
}
|
||||
offset = 0;
|
||||
while (offset < HEIGHT) {
|
||||
String biome = BIOMES.get(random.nextInt(BIOMES.size()));
|
||||
String copy = random.nextBoolean() ? biome : new String(biome);
|
||||
int end = Math.min(HEIGHT, offset + 4 + random.nextInt(96));
|
||||
while (offset < end) {
|
||||
biomes[column][offset++] = copy;
|
||||
}
|
||||
}
|
||||
ground[column] = sample % 4 == 0 ? -1 : random.nextInt(HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt groundSurface(int localX, int localZ) {
|
||||
int value = ground[localX * 16 + localZ];
|
||||
return value < 0 ? OptionalInt.empty() : OptionalInt.of(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
|
||||
return voxels[localX * 16 + localZ][worldY - MINIMUM_Y];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String biome(int localX, int worldY, int localZ) {
|
||||
return biomes[localX * 16 + localZ][worldY - MINIMUM_Y];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user