mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Shared object placement layout
- Object placement bakes split into one per-object layout (palette, tiles, canopy) and per-variant offsets - Variants beyond the per-object limit are built transiently instead of clearing the variant map - High-variant golden and no-rebuild tests
This commit is contained in:
1 parent
d7cad729e4
commit
bf9e65252c
4 files changed
+244
-121
No files matched your search
@@ -146,6 +146,7 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
transient final AtomicCache<KList<IrisBlockVector>> surfaceSupportOffsets = new AtomicCache<>();
|
transient final AtomicCache<KList<IrisBlockVector>> surfaceSupportOffsets = new AtomicCache<>();
|
||||||
transient final AtomicCache<FloatingObjectFootprint> floatingFootprint = new AtomicCache<>();
|
transient final AtomicCache<FloatingObjectFootprint> floatingFootprint = new AtomicCache<>();
|
||||||
transient final ConcurrentHashMap<ObjectPlacementBake.Key, ObjectPlacementBake> placementBakes = new ConcurrentHashMap<>();
|
transient final ConcurrentHashMap<ObjectPlacementBake.Key, ObjectPlacementBake> placementBakes = new ConcurrentHashMap<>();
|
||||||
|
transient volatile ObjectPlacementBake.Layout placementLayout;
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private transient TreeGrounding treeGrounding;
|
private transient TreeGrounding treeGrounding;
|
||||||
|
|||||||
@@ -15,140 +15,73 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
final class ObjectPlacementBake {
|
final class ObjectPlacementBake {
|
||||||
private static final int MAXIMUM_VARIANTS_PER_OBJECT = 32;
|
private static final int MAXIMUM_VARIANTS_PER_OBJECT = 32;
|
||||||
|
|
||||||
private final long blocksRevision;
|
private final Layout layout;
|
||||||
private final long statesRevision;
|
private final Key key;
|
||||||
private final TreeGrounding grounding;
|
|
||||||
private final int count;
|
|
||||||
private final int[] offsetX;
|
private final int[] offsetX;
|
||||||
private final int[] offsetY;
|
private final int[] offsetY;
|
||||||
private final int[] offsetZ;
|
private final int[] offsetZ;
|
||||||
private final int[] layerY;
|
private final int[] layerY;
|
||||||
private final short[] paletteIndexes;
|
|
||||||
private final NativeBlockState[] palette;
|
|
||||||
private final int[] tileBlocks;
|
|
||||||
private final TileData[] tileValues;
|
|
||||||
private final long[] canopy;
|
|
||||||
private final Key key;
|
|
||||||
private volatile double[] exact;
|
private volatile double[] exact;
|
||||||
|
|
||||||
private ObjectPlacementBake(Key key, long blocksRevision, long statesRevision, TreeGrounding grounding, int count,
|
private ObjectPlacementBake(Layout layout, Key key, int[] offsetX, int[] offsetY, int[] offsetZ, int[] layerY) {
|
||||||
int[] offsetX, int[] offsetY, int[] offsetZ, int[] layerY, short[] paletteIndexes,
|
this.layout = layout;
|
||||||
NativeBlockState[] palette, int[] tileBlocks, TileData[] tileValues, long[] canopy) {
|
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.blocksRevision = blocksRevision;
|
|
||||||
this.statesRevision = statesRevision;
|
|
||||||
this.grounding = grounding;
|
|
||||||
this.count = count;
|
|
||||||
this.offsetX = offsetX;
|
this.offsetX = offsetX;
|
||||||
this.offsetY = offsetY;
|
this.offsetY = offsetY;
|
||||||
this.offsetZ = offsetZ;
|
this.offsetZ = offsetZ;
|
||||||
this.layerY = layerY;
|
this.layerY = layerY;
|
||||||
this.paletteIndexes = paletteIndexes;
|
|
||||||
this.palette = palette;
|
|
||||||
this.tileBlocks = tileBlocks;
|
|
||||||
this.tileValues = tileValues;
|
|
||||||
this.canopy = canopy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ObjectPlacementBake of(IrisObject object, SpinKernel spin, boolean ceilingHang, IrisBlockVector translateOffset) {
|
static ObjectPlacementBake of(IrisObject object, SpinKernel spin, boolean ceilingHang, IrisBlockVector translateOffset) {
|
||||||
Key key = Key.of(spin, ceilingHang, translateOffset);
|
Key key = Key.of(spin, ceilingHang, translateOffset);
|
||||||
ConcurrentHashMap<Key, ObjectPlacementBake> variants = object.placementBakes;
|
|
||||||
ObjectPlacementBake cached = variants.get(key);
|
|
||||||
if (cached != null && cached.matches(object)) {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
ObjectPlacementBake built = bake(object, key, spin, ceilingHang, translateOffset);
|
|
||||||
if (built.matches(object)) {
|
|
||||||
if (variants.size() >= MAXIMUM_VARIANTS_PER_OBJECT) {
|
|
||||||
variants.clear();
|
|
||||||
}
|
|
||||||
variants.put(key, built);
|
|
||||||
}
|
|
||||||
return built;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPlacementBake bake(IrisObject object, Key key, SpinKernel spin, boolean ceilingHang,
|
|
||||||
IrisBlockVector translateOffset) {
|
|
||||||
object.readLock.lock();
|
object.readLock.lock();
|
||||||
try {
|
try {
|
||||||
VectorMap<NativeBlockState> blocks = object.blocks;
|
Layout layout = Layout.of(object);
|
||||||
VectorMap<TileData> states = object.states;
|
ConcurrentHashMap<Key, ObjectPlacementBake> variants = object.placementBakes;
|
||||||
long blocksRevision = blocks.modificationRevision();
|
ObjectPlacementBake cached = variants.get(key);
|
||||||
long statesRevision = states.modificationRevision();
|
if (cached != null && cached.layout == layout) {
|
||||||
TreeGrounding grounding = object.getTreeGrounding();
|
return cached;
|
||||||
boolean hasStates = !states.isEmpty();
|
|
||||||
IntArrayList x = new IntArrayList();
|
|
||||||
IntArrayList y = new IntArrayList();
|
|
||||||
IntArrayList z = new IntArrayList();
|
|
||||||
IntArrayList layer = new IntArrayList();
|
|
||||||
IntArrayList indexes = new IntArrayList();
|
|
||||||
IntArrayList tileBlocks = new IntArrayList();
|
|
||||||
List<TileData> tileValues = new ArrayList<>();
|
|
||||||
List<NativeBlockState> palette = new ArrayList<>();
|
|
||||||
IdentityHashMap<NativeBlockState, Integer> paletteSlots = new IdentityHashMap<>();
|
|
||||||
IntArrayList canopyBlocks = new IntArrayList();
|
|
||||||
boolean layerDiffers = false;
|
|
||||||
IrisBlockVector rotated = new IrisBlockVector(0, 0, 0);
|
|
||||||
VectorMap<NativeBlockState>.Cursor cursor = blocks.cursor();
|
|
||||||
int index = 0;
|
|
||||||
while (cursor.next()) {
|
|
||||||
IrisBlockVector source = cursor.key();
|
|
||||||
NativeBlockState state = cursor.value();
|
|
||||||
if (state == null) {
|
|
||||||
state = IrisObject.States.air();
|
|
||||||
}
|
|
||||||
transform(source, rotated, spin, ceilingHang, translateOffset);
|
|
||||||
int roundedY = (int) Math.round(rotated.getY());
|
|
||||||
int floorY = rotated.getBlockY();
|
|
||||||
x.add((int) Math.round(rotated.getX()));
|
|
||||||
y.add(roundedY);
|
|
||||||
z.add((int) Math.round(rotated.getZ()));
|
|
||||||
layer.add(floorY);
|
|
||||||
layerDiffers |= floorY != roundedY;
|
|
||||||
Integer slot = paletteSlots.get(state);
|
|
||||||
if (slot == null) {
|
|
||||||
slot = palette.size();
|
|
||||||
palette.add(state);
|
|
||||||
paletteSlots.put(state, slot);
|
|
||||||
}
|
|
||||||
indexes.add(slot);
|
|
||||||
if (hasStates) {
|
|
||||||
TileData tile = states.get(source);
|
|
||||||
if (tile != null) {
|
|
||||||
tileBlocks.add(index);
|
|
||||||
tileValues.add(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (grounding != null && grounding.isCanopy(source.getBlockX(), source.getBlockY(), source.getBlockZ())) {
|
|
||||||
canopyBlocks.add(index);
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
if (palette.size() > Short.MAX_VALUE) {
|
ObjectPlacementBake built = bake(object, layout, key, spin, ceilingHang, translateOffset);
|
||||||
throw new IllegalStateException("Object " + object.getLoadKey() + " has more than "
|
if (cached != null) {
|
||||||
+ Short.MAX_VALUE + " distinct block states");
|
variants.replace(key, cached, built);
|
||||||
|
} else if (variants.size() < MAXIMUM_VARIANTS_PER_OBJECT) {
|
||||||
|
variants.putIfAbsent(key, built);
|
||||||
}
|
}
|
||||||
short[] paletteIndexes = new short[index];
|
return built;
|
||||||
for (int block = 0; block < index; block++) {
|
|
||||||
paletteIndexes[block] = (short) indexes.getInt(block);
|
|
||||||
}
|
|
||||||
long[] canopy = null;
|
|
||||||
if (!canopyBlocks.isEmpty()) {
|
|
||||||
canopy = new long[(index + 63) >>> 6];
|
|
||||||
for (int block : canopyBlocks) {
|
|
||||||
canopy[block >>> 6] |= 1L << (block & 63);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new ObjectPlacementBake(key, blocksRevision, statesRevision, grounding, index,
|
|
||||||
x.toIntArray(), y.toIntArray(), z.toIntArray(), layerDiffers ? layer.toIntArray() : null,
|
|
||||||
paletteIndexes, palette.toArray(new NativeBlockState[0]),
|
|
||||||
tileBlocks.isEmpty() ? null : tileBlocks.toIntArray(),
|
|
||||||
tileValues.isEmpty() ? null : tileValues.toArray(new TileData[0]), canopy);
|
|
||||||
} finally {
|
} finally {
|
||||||
object.readLock.unlock();
|
object.readLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ObjectPlacementBake bake(IrisObject object, Layout layout, Key key, SpinKernel spin, boolean ceilingHang,
|
||||||
|
IrisBlockVector translateOffset) {
|
||||||
|
int count = layout.count;
|
||||||
|
int[] x = new int[count];
|
||||||
|
int[] y = new int[count];
|
||||||
|
int[] z = new int[count];
|
||||||
|
int[] layer = new int[count];
|
||||||
|
boolean layerDiffers = false;
|
||||||
|
IrisBlockVector rotated = new IrisBlockVector(0, 0, 0);
|
||||||
|
VectorMap<NativeBlockState>.Cursor cursor = object.blocks.cursor();
|
||||||
|
int index = 0;
|
||||||
|
while (cursor.next() && index < count) {
|
||||||
|
transform(cursor.key(), rotated, spin, ceilingHang, translateOffset);
|
||||||
|
int roundedY = (int) Math.round(rotated.getY());
|
||||||
|
int floorY = rotated.getBlockY();
|
||||||
|
x[index] = (int) Math.round(rotated.getX());
|
||||||
|
y[index] = roundedY;
|
||||||
|
z[index] = (int) Math.round(rotated.getZ());
|
||||||
|
layer[index] = floorY;
|
||||||
|
layerDiffers |= floorY != roundedY;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (index != count) {
|
||||||
|
throw new IllegalStateException("Object " + object.getLoadKey() + " changed while its placement bake was built");
|
||||||
|
}
|
||||||
|
return new ObjectPlacementBake(layout, key, x, y, z, layerDiffers ? layer : null);
|
||||||
|
}
|
||||||
|
|
||||||
private static void transform(IrisBlockVector source, IrisBlockVector target, SpinKernel spin, boolean ceilingHang,
|
private static void transform(IrisBlockVector source, IrisBlockVector target, SpinKernel spin, boolean ceilingHang,
|
||||||
IrisBlockVector translateOffset) {
|
IrisBlockVector translateOffset) {
|
||||||
target.setX(source.getX());
|
target.setX(source.getX());
|
||||||
@@ -163,14 +96,8 @@ final class ObjectPlacementBake {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean matches(IrisObject object) {
|
|
||||||
return object.blocks.modificationRevision() == blocksRevision
|
|
||||||
&& object.states.modificationRevision() == statesRevision
|
|
||||||
&& object.getTreeGrounding() == grounding;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count() {
|
int count() {
|
||||||
return count;
|
return layout.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
int offsetX(int index) {
|
int offsetX(int index) {
|
||||||
@@ -190,22 +117,23 @@ final class ObjectPlacementBake {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int paletteIndex(int index) {
|
int paletteIndex(int index) {
|
||||||
return paletteIndexes[index];
|
return layout.paletteIndexes[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBlockState[] palette() {
|
NativeBlockState[] palette() {
|
||||||
return palette;
|
return layout.palette;
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] tileBlocks() {
|
int[] tileBlocks() {
|
||||||
return tileBlocks;
|
return layout.tileBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
TileData[] tileValues() {
|
TileData[] tileValues() {
|
||||||
return tileValues;
|
return layout.tileValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canopy(int index) {
|
boolean canopy(int index) {
|
||||||
|
long[] canopy = layout.canopy;
|
||||||
return canopy != null && (canopy[index >>> 6] & (1L << (index & 63))) != 0L;
|
return canopy != null && (canopy[index >>> 6] & (1L << (index & 63))) != 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +142,7 @@ final class ObjectPlacementBake {
|
|||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
int count = layout.count;
|
||||||
object.readLock.lock();
|
object.readLock.lock();
|
||||||
try {
|
try {
|
||||||
resolved = new double[count * 3];
|
resolved = new double[count * 3];
|
||||||
@@ -237,6 +166,112 @@ final class ObjectPlacementBake {
|
|||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static final class Layout {
|
||||||
|
private final long blocksRevision;
|
||||||
|
private final long statesRevision;
|
||||||
|
private final TreeGrounding grounding;
|
||||||
|
private final int count;
|
||||||
|
private final short[] paletteIndexes;
|
||||||
|
private final NativeBlockState[] palette;
|
||||||
|
private final int[] tileBlocks;
|
||||||
|
private final TileData[] tileValues;
|
||||||
|
private final long[] canopy;
|
||||||
|
|
||||||
|
private Layout(long blocksRevision, long statesRevision, TreeGrounding grounding, int count, short[] paletteIndexes,
|
||||||
|
NativeBlockState[] palette, int[] tileBlocks, TileData[] tileValues, long[] canopy) {
|
||||||
|
this.blocksRevision = blocksRevision;
|
||||||
|
this.statesRevision = statesRevision;
|
||||||
|
this.grounding = grounding;
|
||||||
|
this.count = count;
|
||||||
|
this.paletteIndexes = paletteIndexes;
|
||||||
|
this.palette = palette;
|
||||||
|
this.tileBlocks = tileBlocks;
|
||||||
|
this.tileValues = tileValues;
|
||||||
|
this.canopy = canopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Layout of(IrisObject object) {
|
||||||
|
Layout current = object.placementLayout;
|
||||||
|
if (current != null && current.matches(object)) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
Layout built = build(object);
|
||||||
|
if (!built.matches(object)) {
|
||||||
|
return built;
|
||||||
|
}
|
||||||
|
object.placementLayout = built;
|
||||||
|
if (current != null) {
|
||||||
|
object.placementBakes.clear();
|
||||||
|
}
|
||||||
|
return built;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Layout build(IrisObject object) {
|
||||||
|
VectorMap<NativeBlockState> blocks = object.blocks;
|
||||||
|
VectorMap<TileData> states = object.states;
|
||||||
|
long blocksRevision = blocks.modificationRevision();
|
||||||
|
long statesRevision = states.modificationRevision();
|
||||||
|
TreeGrounding grounding = object.getTreeGrounding();
|
||||||
|
boolean hasStates = !states.isEmpty();
|
||||||
|
int size = blocks.size();
|
||||||
|
short[] paletteIndexes = new short[size];
|
||||||
|
IntArrayList tileBlocks = new IntArrayList();
|
||||||
|
List<TileData> tileValues = new ArrayList<>();
|
||||||
|
List<NativeBlockState> palette = new ArrayList<>();
|
||||||
|
IdentityHashMap<NativeBlockState, Integer> paletteSlots = new IdentityHashMap<>();
|
||||||
|
long[] canopy = null;
|
||||||
|
VectorMap<NativeBlockState>.Cursor cursor = blocks.cursor();
|
||||||
|
int index = 0;
|
||||||
|
while (cursor.next()) {
|
||||||
|
IrisBlockVector source = cursor.key();
|
||||||
|
NativeBlockState state = cursor.value();
|
||||||
|
if (state == null) {
|
||||||
|
state = IrisObject.States.air();
|
||||||
|
}
|
||||||
|
Integer slot = paletteSlots.get(state);
|
||||||
|
if (slot == null) {
|
||||||
|
slot = palette.size();
|
||||||
|
if (slot >= Short.MAX_VALUE) {
|
||||||
|
throw new IllegalStateException("Object " + object.getLoadKey() + " has more than "
|
||||||
|
+ Short.MAX_VALUE + " distinct block states");
|
||||||
|
}
|
||||||
|
palette.add(state);
|
||||||
|
paletteSlots.put(state, slot);
|
||||||
|
}
|
||||||
|
if (index == paletteIndexes.length) {
|
||||||
|
throw new IllegalStateException("Object " + object.getLoadKey() + " changed while its placement layout was built");
|
||||||
|
}
|
||||||
|
paletteIndexes[index] = (short) slot.intValue();
|
||||||
|
if (hasStates) {
|
||||||
|
TileData tile = states.get(source);
|
||||||
|
if (tile != null) {
|
||||||
|
tileBlocks.add(index);
|
||||||
|
tileValues.add(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (grounding != null && grounding.isCanopy(source.getBlockX(), source.getBlockY(), source.getBlockZ())) {
|
||||||
|
if (canopy == null) {
|
||||||
|
canopy = new long[(size + 63) >>> 6];
|
||||||
|
}
|
||||||
|
canopy[index >>> 6] |= 1L << (index & 63);
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (index != size) {
|
||||||
|
throw new IllegalStateException("Object " + object.getLoadKey() + " changed while its placement layout was built");
|
||||||
|
}
|
||||||
|
return new Layout(blocksRevision, statesRevision, grounding, index, paletteIndexes,
|
||||||
|
palette.toArray(new NativeBlockState[0]), tileBlocks.isEmpty() ? null : tileBlocks.toIntArray(),
|
||||||
|
tileValues.isEmpty() ? null : tileValues.toArray(new TileData[0]), canopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(IrisObject object) {
|
||||||
|
return object.blocks.modificationRevision() == blocksRevision
|
||||||
|
&& object.states.modificationRevision() == statesRevision
|
||||||
|
&& object.getTreeGrounding() == grounding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
record Key(boolean rotates, int xMode, int yMode, int zMode, long xCos, long xSin, long yCos, long ySin,
|
record Key(boolean rotates, int xMode, int yMode, int zMode, long xCos, long xSin, long yCos, long ySin,
|
||||||
long zCos, long zSin, boolean ceilingHang, boolean translating, long translateX, long translateY,
|
long zCos, long zSin, boolean ceilingHang, boolean translating, long translateX, long translateY,
|
||||||
long translateZ) {
|
long translateZ) {
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ import art.arcane.iris.generation.geometry.IrisBlockVector;
|
|||||||
import art.arcane.iris.testsupport.KeyedBlockState;
|
import art.arcane.iris.testsupport.KeyedBlockState;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotSame;
|
import static org.junit.Assert.assertNotSame;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class ObjectPlacementBakeTest {
|
public class ObjectPlacementBakeTest {
|
||||||
@Test
|
@Test
|
||||||
@@ -65,6 +72,57 @@ public class ObjectPlacementBakeTest {
|
|||||||
assertSame(after, ObjectPlacementBake.of(object, spin, false, null));
|
assertSame(after, ObjectPlacementBake.of(object, spin, false, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void highVariantPlacementsKeepCachedVariantsAndNeverRebuildTheLayout() {
|
||||||
|
IrisObject object = object();
|
||||||
|
IrisObjectRotation pine = new IrisObjectRotation();
|
||||||
|
pine.setEnabled(true);
|
||||||
|
pine.setXAxis(new IrisAxisRotationClamp(true, false, -9D, 9D, 1D));
|
||||||
|
pine.setYAxis(new IrisAxisRotationClamp(true, false, 0D, 270D, 90D));
|
||||||
|
pine.setZAxis(new IrisAxisRotationClamp(true, false, -9D, 9D, 1D));
|
||||||
|
IrisBlockVector translate = new IrisBlockVector(0D, -6D, 0D);
|
||||||
|
Random random = new Random(1444L);
|
||||||
|
ObjectPlacementBake first = ObjectPlacementBake.of(object, new SpinKernel(pine, 0, 0, 0), false, translate);
|
||||||
|
Map<ObjectPlacementBake.Key, ObjectPlacementBake> settled = null;
|
||||||
|
Set<ObjectPlacementBake.Key> distinct = new HashSet<>();
|
||||||
|
for (int placement = 0; placement < 4000; placement++) {
|
||||||
|
SpinKernel spin = new SpinKernel(pine, random.nextInt(360), random.nextInt(360), random.nextInt(360));
|
||||||
|
ObjectPlacementBake bake = ObjectPlacementBake.of(object, spin, false, translate);
|
||||||
|
distinct.add(ObjectPlacementBake.Key.of(spin, false, translate));
|
||||||
|
assertSame(first.palette(), bake.palette());
|
||||||
|
assertTrue(object.placementBakes.size() <= 32);
|
||||||
|
assertTransformed(object, spin, translate, bake);
|
||||||
|
if (placement == 2000) {
|
||||||
|
settled = new HashMap<>(object.placementBakes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(distinct.size() > 40);
|
||||||
|
assertEquals(32, settled.size());
|
||||||
|
assertEquals(settled.keySet(), object.placementBakes.keySet());
|
||||||
|
for (Map.Entry<ObjectPlacementBake.Key, ObjectPlacementBake> entry : settled.entrySet()) {
|
||||||
|
assertSame(entry.getValue(), object.placementBakes.get(entry.getKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertTransformed(IrisObject object, SpinKernel spin, IrisBlockVector translate, ObjectPlacementBake bake) {
|
||||||
|
double[] exact = bake.exact(object, spin);
|
||||||
|
int index = 0;
|
||||||
|
for (IrisBlockVector source : object.getBlocks().keys()) {
|
||||||
|
IrisBlockVector expected = source.clone();
|
||||||
|
spin.rotate(expected);
|
||||||
|
expected.add(translate);
|
||||||
|
assertEquals((int) Math.round(expected.getX()), bake.offsetX(index));
|
||||||
|
assertEquals((int) Math.round(expected.getY()), bake.offsetY(index));
|
||||||
|
assertEquals((int) Math.round(expected.getZ()), bake.offsetZ(index));
|
||||||
|
assertEquals(expected.getBlockY(), bake.layerY(index));
|
||||||
|
assertEquals(expected.getX(), exact[index * 3], 0D);
|
||||||
|
assertEquals(expected.getY(), exact[index * 3 + 1], 0D);
|
||||||
|
assertEquals(expected.getZ(), exact[index * 3 + 2], 0D);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
assertEquals(bake.count(), index);
|
||||||
|
}
|
||||||
|
|
||||||
private static IrisObject object() {
|
private static IrisObject object() {
|
||||||
IrisObject object = new IrisObject(5, 5, 5);
|
IrisObject object = new IrisObject(5, 5, 5);
|
||||||
KeyedBlockState stone = new KeyedBlockState("minecraft:stone");
|
KeyedBlockState stone = new KeyedBlockState("minecraft:stone");
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ public class ObjectPlacementGoldenTest {
|
|||||||
private static final int SEEDS = 5;
|
private static final int SEEDS = 5;
|
||||||
private static final long GOLDEN = 0x408f9d4113015897L;
|
private static final long GOLDEN = 0x408f9d4113015897L;
|
||||||
private static final long MARKER_GOLDEN = 0x60b4bbf1144b239cL;
|
private static final long MARKER_GOLDEN = 0x60b4bbf1144b239cL;
|
||||||
|
private static final long HIGH_VARIANT_GOLDEN = 0xf017622340ecac42L;
|
||||||
|
|
||||||
private static final List<String> PALETTE = List.of(
|
private static final List<String> PALETTE = List.of(
|
||||||
"minecraft:stone",
|
"minecraft:stone",
|
||||||
@@ -229,6 +230,34 @@ public class ObjectPlacementGoldenTest {
|
|||||||
assertEquals(Long.toHexString(MARKER_GOLDEN), Long.toHexString(combined));
|
assertEquals(Long.toHexString(MARKER_GOLDEN), Long.toHexString(combined));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void highVariantPlacementsMatchTheRecordedBlockStream() {
|
||||||
|
Random random = new Random(1444L);
|
||||||
|
IrisObjectRotation pine = rotation(new IrisAxisRotationClamp(true, false, -9D, 9D, 1D),
|
||||||
|
new IrisAxisRotationClamp(true, false, 0D, 270D, 90D), new IrisAxisRotationClamp(true, false, -9D, 9D, 1D));
|
||||||
|
long combined = 29L;
|
||||||
|
int writes = 0;
|
||||||
|
for (int index = 0; index < 16; index++) {
|
||||||
|
IrisObject object = randomObject(random, index);
|
||||||
|
object.setLoader(objectData);
|
||||||
|
IrisObjectPlacement placement = randomPlacement(random).toPlacement("pine");
|
||||||
|
placement.setRotation(pine);
|
||||||
|
for (int seed = 0; seed < 150; seed++) {
|
||||||
|
RNG rng = new RNG(index * 104_729L + seed);
|
||||||
|
GoldenPlacer placer = new GoldenPlacer(engine, (seed & 1) == 0);
|
||||||
|
int y = seed % 3 == 0 ? 90 + (seed & 7) : -1;
|
||||||
|
int result = object.place(300 + seed, y, -400 + index * 7, placer, placement, rng, null, null, data);
|
||||||
|
long hash = mix(placer.hash, result);
|
||||||
|
hash = mix(hash, rng.nextLong());
|
||||||
|
combined = mix(combined, hash);
|
||||||
|
writes += placer.writes;
|
||||||
|
}
|
||||||
|
assertTrue(object.placementBakes.size() <= 32);
|
||||||
|
}
|
||||||
|
assertTrue(writes > 10_000);
|
||||||
|
assertEquals(Long.toHexString(HIGH_VARIANT_GOLDEN), Long.toHexString(combined));
|
||||||
|
}
|
||||||
|
|
||||||
private static long mix(long hash, long value) {
|
private static long mix(long hash, long value) {
|
||||||
long mixed = (hash ^ value) * 0x9E3779B97F4A7C15L;
|
long mixed = (hash ^ value) * 0x9E3779B97F4A7C15L;
|
||||||
return mixed ^ (mixed >>> 29);
|
return mixed ^ (mixed >>> 29);
|
||||||
|
|||||||
Reference in new issue
Block a user