mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Baked object placement
- Bake rotated block offsets, palette indexes, tiles and canopy bits per object and spin kernel - Resolve decay, rotation, waterlog and air facts once per palette entry per placement - Skip the destination read for waterloggable states that cannot become waterlogged - Move SpinKernel to its own file
This commit is contained in:
1 parent
b526b480a7
commit
943c8d67e5
6 files changed
+763
-320
No files matched your search
@@ -61,6 +61,7 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@@ -144,6 +145,7 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
protected transient AtomicCache<AxisAlignedBB> aabb = new AtomicCache<>();
|
protected transient AtomicCache<AxisAlignedBB> aabb = new AtomicCache<>();
|
||||||
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<>();
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private transient TreeGrounding treeGrounding;
|
private transient TreeGrounding treeGrounding;
|
||||||
|
|||||||
+273
-319
@@ -52,6 +52,7 @@ import art.arcane.volmlib.util.matter.MatterMarker;
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -474,128 +475,52 @@ final class IrisObjectPlacementRunner {
|
|||||||
int topLayer = Integer.MIN_VALUE;
|
int topLayer = Integer.MIN_VALUE;
|
||||||
int vacuumLowest = Integer.MAX_VALUE;
|
int vacuumLowest = Integer.MAX_VALUE;
|
||||||
int vacuumHighest = Integer.MIN_VALUE;
|
int vacuumHighest = Integer.MIN_VALUE;
|
||||||
self.readLock.lock();
|
ObjectPlacementBake bake = ObjectPlacementBake.of(self, spin, ceilingHang, translating ? translateOffset : null);
|
||||||
|
PlacementStates palette = new PlacementStates(bake.palette(), config.getRotation(), spinx, spiny, spinz,
|
||||||
|
preventingDecay, placer.isDebugSmartBore(), excludedStiltMaterials);
|
||||||
|
double[] exact = warped || hasEdits || eroding ? bake.exact(self, spin) : null;
|
||||||
|
String[] markers = config.getMarkers().isNotEmpty() && placer.getEngine() != null
|
||||||
|
? resolveMarkers(config, rng, x, yv, z, rdata, bake) : null;
|
||||||
|
int[] tileBlocks = bake.tileBlocks();
|
||||||
|
TileData[] tileValues = bake.tileValues();
|
||||||
|
int tileCursor = 0;
|
||||||
|
int count = bake.count();
|
||||||
|
|
||||||
KMap<IrisBlockVector, String> markers = null;
|
for (int index = 0; index < count; index++) {
|
||||||
|
int entry = bake.paletteIndex(index);
|
||||||
try {
|
|
||||||
VectorMap<NativeBlockState> blocks = self.blocks;
|
|
||||||
VectorMap<TileData> states = self.states;
|
|
||||||
TreeGrounding tree = self.getTreeGrounding();
|
|
||||||
// Zero-tile objects are the common case: skip the two Key allocations VectorMap#get needs to prove null.
|
|
||||||
boolean hasStates = !states.isEmpty();
|
|
||||||
|
|
||||||
if (config.getMarkers().isNotEmpty() && placer.getEngine() != null) {
|
|
||||||
markers = new KMap<>();
|
|
||||||
KList<IrisBlockVector> list = StreamSupport.stream(blocks.keys().spliterator(), false)
|
|
||||||
.collect(KList.collector());
|
|
||||||
// Marker selection persists into the mantle, so it must be seed-deterministic.
|
|
||||||
// Derive a side stream keyed on the placement position instead of consuming the
|
|
||||||
// caller's rng: consuming draws there would shift every later placement in the
|
|
||||||
// chunk and invalidate existing worlds/goldenhashes.
|
|
||||||
RNG markerRng = rng.nextParallelRNG(((((long) x) << 32) | (z & 0xFFFFFFFFL)) * 31L + yv);
|
|
||||||
|
|
||||||
int markerIndex = 0;
|
|
||||||
for (IrisObjectMarker j : config.getMarkers()) {
|
|
||||||
IrisMarker marker = self.getLoader().getMarkerLoader().load(j.getMarker());
|
|
||||||
int markerSalt = markerIndex++;
|
|
||||||
|
|
||||||
if (marker == null || marker.isCompatExcluded()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int max = j.getMaximumMarkers();
|
|
||||||
for (IrisBlockVector i : list.shuffleCopy(markerRng.nextParallelRNG(markerSalt))) {
|
|
||||||
if (max <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBlockState data = blocks.get(i);
|
|
||||||
if (data == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (NativeBlockState k : j.getMark(rdata)) {
|
|
||||||
if (max <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.isExact() ? k.matches(data) : IrisObjectShaping.materialKey(k).equals(IrisObjectShaping.materialKey(data))) {
|
|
||||||
boolean a = !blocks.containsKey((IrisBlockVector) i.clone().add(new IrisBlockVector(0, 1, 0)));
|
|
||||||
boolean fff = !blocks.containsKey((IrisBlockVector) i.clone().add(new IrisBlockVector(0, 2, 0)));
|
|
||||||
|
|
||||||
if (!marker.isEmptyAbove() || (a && fff)) {
|
|
||||||
markers.put(i, j.getMarker());
|
|
||||||
max--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VectorMap<NativeBlockState>.Cursor cursor = blocks.cursor();
|
|
||||||
while (cursor.next()) {
|
|
||||||
IrisBlockVector g = cursor.key();
|
|
||||||
NativeBlockState d;
|
|
||||||
TileData tile = null;
|
TileData tile = null;
|
||||||
|
if (tileBlocks != null && tileCursor < tileBlocks.length && tileBlocks[tileCursor] == index) {
|
||||||
try {
|
tile = tileValues[tileCursor++];
|
||||||
d = cursor.value();
|
|
||||||
if (hasStates) {
|
|
||||||
tile = states.get(g);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
GenerationFailures.rethrowEngineFailure(e);
|
|
||||||
IrisLogging.reportError(e);
|
|
||||||
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (cme)");
|
|
||||||
d = IrisObject.States.air();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d == null) {
|
if (stilting && palette.stiltLayer(entry)) {
|
||||||
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (null)");
|
int layer = bake.layerY(index);
|
||||||
d = IrisObject.States.air();
|
if (layer < lowest) {
|
||||||
|
lowest = layer;
|
||||||
}
|
}
|
||||||
|
if (layer > topLayer) {
|
||||||
if (placer.isDebugSmartBore() && IrisObject.States.vair().equals(d)) {
|
topLayer = layer;
|
||||||
d = IrisObject.States.vairDebug();
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBlockState data = d;
|
|
||||||
IrisBlockVector i = g.clone();
|
|
||||||
spin.rotate(i);
|
|
||||||
if (ceilingHang) {
|
|
||||||
i.setY(-i.getBlockY());
|
|
||||||
}
|
|
||||||
if (translating) {
|
|
||||||
i.add(translateOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stilting && IrisObjectShaping.isStiltLayerBlock(data)) {
|
|
||||||
if (i.getBlockY() < lowest) {
|
|
||||||
lowest = i.getBlockY();
|
|
||||||
}
|
|
||||||
if (i.getBlockY() > topLayer) {
|
|
||||||
topLayer = i.getBlockY();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preventingDecay && IrisProceduralBlocks.hasProperty(data, "distance") && "false".equals(IrisProceduralBlocks.propertyValue(data, "persistent"))) {
|
NativeBlockState data = palette.decayed(entry);
|
||||||
data = data.withProperty("persistent", "true");
|
boolean edited = false;
|
||||||
}
|
|
||||||
|
|
||||||
if (hasEdits) {
|
if (hasEdits) {
|
||||||
|
double exactX = exact[index * 3];
|
||||||
|
double exactY = exact[index * 3 + 1];
|
||||||
|
double exactZ = exact[index * 3 + 2];
|
||||||
for (IrisObjectReplace j : config.getEdit()) {
|
for (IrisObjectReplace j : config.getEdit()) {
|
||||||
if (rng.chance(j.getChance())) {
|
if (rng.chance(j.getChance())) {
|
||||||
for (NativeBlockState k : j.getFind(rdata)) {
|
for (NativeBlockState k : j.getFind(rdata)) {
|
||||||
if (j.isExact() ? k.matches(data) : IrisObjectShaping.materialKey(k).equals(IrisObjectShaping.materialKey(data))) {
|
if (j.isExact() ? k.matches(data) : IrisObjectShaping.materialKey(k).equals(IrisObjectShaping.materialKey(data))) {
|
||||||
NativeBlockState newData = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata);
|
NativeBlockState newData = j.getReplace(rng, exactX + x, exactY + y, exactZ + z, rdata);
|
||||||
|
|
||||||
boolean sameMaterial = IrisObjectShaping.materialKey(newData).equals(IrisObjectShaping.materialKey(data));
|
boolean sameMaterial = IrisObjectShaping.materialKey(newData).equals(IrisObjectShaping.materialKey(data));
|
||||||
if (sameMaterial && !(newData.isCustom() || data.isCustom()))
|
if (sameMaterial && !(newData.isCustom() || data.isCustom()))
|
||||||
data = BlockDataMergeSupport.merge(data, newData);
|
data = BlockDataMergeSupport.merge(data, newData);
|
||||||
else
|
else
|
||||||
data = newData;
|
data = newData;
|
||||||
|
edited = true;
|
||||||
|
|
||||||
Optional<TileData> t = j.getReplace().getTile(rng, x, y, z, rdata);
|
Optional<TileData> t = j.getReplace().getTile(rng, x, y, z, rdata);
|
||||||
if (t.isPresent()) {
|
if (t.isPresent()) {
|
||||||
@@ -609,18 +534,21 @@ final class IrisObjectPlacementRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data = config.getRotation().rotate(data, spinx, spiny, spinz);
|
StateFacts facts = edited ? palette.factsOf(data) : palette.facts(entry);
|
||||||
if (data == null) {
|
NativeBlockState rotated = facts.rotated();
|
||||||
|
if (rotated == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
xx = x + (int) Math.round(i.getX());
|
xx = x + bake.offsetX(index);
|
||||||
|
int yy = y + bake.offsetY(index);
|
||||||
int yy = y + (int) Math.round(i.getY());
|
zz = z + bake.offsetZ(index);
|
||||||
zz = z + (int) Math.round(i.getZ());
|
|
||||||
|
|
||||||
if (warped) {
|
if (warped) {
|
||||||
xx += surfaceWarp.fitDouble(-warpHalf, warpHalf, i.getX() + x, i.getY() + y, i.getZ() + z);
|
double exactX = exact[index * 3];
|
||||||
zz += surfaceWarp.fitDouble(-warpHalf, warpHalf, i.getZ() + z, i.getY() + y, i.getX() + x);
|
double exactY = exact[index * 3 + 1];
|
||||||
|
double exactZ = exact[index * 3 + 2];
|
||||||
|
xx += surfaceWarp.fitDouble(-warpHalf, warpHalf, exactX + x, exactY + y, exactZ + z);
|
||||||
|
zz += surfaceWarp.fitDouble(-warpHalf, warpHalf, exactZ + z, exactY + y, exactX + x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paint) {
|
if (paint) {
|
||||||
@@ -628,8 +556,8 @@ final class IrisObjectPlacementRunner {
|
|||||||
if (surface == IrisPaintSurfaceProjection.MISSING) {
|
if (surface == IrisPaintSurfaceProjection.MISSING) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!B.isVineBlock(data)) {
|
if (!B.isVineBlock(rotated)) {
|
||||||
yy = (int) Math.round(i.getY()) + Math.floorDiv(self.h, 2) + surface + yrand;
|
yy = bake.offsetY(index) + Math.floorDiv(self.h, 2) + surface + yrand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,13 +565,18 @@ final class IrisObjectPlacementRunner {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canopy = tree != null && tree.isCanopy(g.getBlockX(), g.getBlockY(), g.getBlockZ());
|
boolean canopy = bake.canopy(index);
|
||||||
if (canopy && !placer.beginTreeCanopy(new BlockPosition(xx, yy, zz))) {
|
if (canopy && !placer.beginTreeCanopy(new BlockPosition(xx, yy, zz))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = normalizePlacedWaterlogging(data, placer, xx, yy, zz, waterlogCandidate);
|
data = rotated;
|
||||||
|
if (facts.waterloggable()) {
|
||||||
|
data = waterlogCandidate || facts.authoredWaterlogged()
|
||||||
|
? IrisProceduralBlocks.normalizeWaterlogging(data, placer.get(xx, yy, zz), waterlogCandidate)
|
||||||
|
: facts.dry();
|
||||||
|
}
|
||||||
|
|
||||||
if (!rawStructurePiece && B.isVineBlock(data)) {
|
if (!rawStructurePiece && B.isVineBlock(data)) {
|
||||||
data = attachVineFaces(placer, data, xx, yy, zz);
|
data = attachVineFaces(placer, data, xx, yy, zz);
|
||||||
@@ -652,9 +585,7 @@ final class IrisObjectPlacementRunner {
|
|||||||
// Short-circuit order matters for cost only: the mantle read is paid solely
|
// Short-circuit order matters for cost only: the mantle read is paid solely
|
||||||
// for vine blocks. Both operands are pure, so the value is unchanged.
|
// for vine blocks. Both operands are pure, so the value is unchanged.
|
||||||
boolean wouldReplace = !rawStructurePiece && B.isVineBlock(data) && B.isSolid(placer.get(xx, yy, zz));
|
boolean wouldReplace = !rawStructurePiece && B.isVineBlock(data) && B.isSolid(placer.get(xx, yy, zz));
|
||||||
String material = IrisObjectShaping.materialKey(data);
|
boolean place = shouldPlaceObjectBlock(rawStructurePiece, facts.air(), wouldReplace);
|
||||||
boolean air = material.equals("minecraft:air") || material.equals("minecraft:cave_air");
|
|
||||||
boolean place = shouldPlaceObjectBlock(rawStructurePiece, air, wouldReplace);
|
|
||||||
|
|
||||||
if (data.isCustom() || place) {
|
if (data.isCustom() || place) {
|
||||||
placer.set(xx, yy, zz, data);
|
placer.set(xx, yy, zz, data);
|
||||||
@@ -668,8 +599,8 @@ final class IrisObjectPlacementRunner {
|
|||||||
if (tile != null) {
|
if (tile != null) {
|
||||||
placer.setTile(xx, yy, zz, tile);
|
placer.setTile(xx, yy, zz, tile);
|
||||||
}
|
}
|
||||||
if (markers != null && markers.containsKey(g)) {
|
if (markers != null && markers[index] != null) {
|
||||||
placer.setData(xx, yy, zz, new MatterMarker(markers.get(g)));
|
placer.setData(xx, yy, zz, new MatterMarker(markers[index]));
|
||||||
}
|
}
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.accept(new BlockPosition(xx, yy, zz), data);
|
listener.accept(new BlockPosition(xx, yy, zz), data);
|
||||||
@@ -687,9 +618,6 @@ final class IrisObjectPlacementRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
self.readLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (grounding != null) {
|
if (grounding != null) {
|
||||||
for (TreeGrounding.Extension extension : grounding.extensions()) {
|
for (TreeGrounding.Extension extension : grounding.extensions()) {
|
||||||
@@ -705,9 +633,6 @@ final class IrisObjectPlacementRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stilting) {
|
if (stilting) {
|
||||||
self.readLock.lock();
|
|
||||||
try {
|
|
||||||
VectorMap<NativeBlockState> blocks = self.blocks;
|
|
||||||
IrisStiltSettings settings = stiltSettings;
|
IrisStiltSettings settings = stiltSettings;
|
||||||
|
|
||||||
double erodeCentroidX = 0;
|
double erodeCentroidX = 0;
|
||||||
@@ -715,38 +640,21 @@ final class IrisObjectPlacementRunner {
|
|||||||
double erodeMaxDist = 1;
|
double erodeMaxDist = 1;
|
||||||
if (eroding) {
|
if (eroding) {
|
||||||
int centroidCount = 0;
|
int centroidCount = 0;
|
||||||
VectorMap<NativeBlockState>.Cursor centroidCursor = blocks.cursor();
|
for (int index = 0; index < count; index++) {
|
||||||
while (centroidCursor.next()) {
|
if (bake.layerY(index) == lowest && palette.shouldStilt(bake.paletteIndex(index))) {
|
||||||
IrisBlockVector rot = centroidCursor.key().clone();
|
erodeCentroidX += exact[index * 3];
|
||||||
spin.rotate(rot);
|
erodeCentroidZ += exact[index * 3 + 2];
|
||||||
if (translating) {
|
|
||||||
rot.add(translateOffset);
|
|
||||||
}
|
|
||||||
if (rot.getBlockY() == lowest) {
|
|
||||||
NativeBlockState bd = centroidCursor.value();
|
|
||||||
if (bd != null && IrisObjectShaping.shouldStilt(bd, excludedStiltMaterials)) {
|
|
||||||
erodeCentroidX += rot.getX();
|
|
||||||
erodeCentroidZ += rot.getZ();
|
|
||||||
centroidCount++;
|
centroidCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (centroidCount > 0) {
|
if (centroidCount > 0) {
|
||||||
erodeCentroidX /= centroidCount;
|
erodeCentroidX /= centroidCount;
|
||||||
erodeCentroidZ /= centroidCount;
|
erodeCentroidZ /= centroidCount;
|
||||||
}
|
}
|
||||||
VectorMap<NativeBlockState>.Cursor spreadCursor = blocks.cursor();
|
for (int index = 0; index < count; index++) {
|
||||||
while (spreadCursor.next()) {
|
if (bake.layerY(index) == lowest && palette.shouldStilt(bake.paletteIndex(index))) {
|
||||||
IrisBlockVector rot = spreadCursor.key().clone();
|
double dx = exact[index * 3] - erodeCentroidX;
|
||||||
spin.rotate(rot);
|
double dz = exact[index * 3 + 2] - erodeCentroidZ;
|
||||||
if (translating) {
|
|
||||||
rot.add(translateOffset);
|
|
||||||
}
|
|
||||||
if (rot.getBlockY() == lowest) {
|
|
||||||
NativeBlockState bd = spreadCursor.value();
|
|
||||||
if (bd != null && IrisObjectShaping.shouldStilt(bd, excludedStiltMaterials)) {
|
|
||||||
double dx = rot.getX() - erodeCentroidX;
|
|
||||||
double dz = rot.getZ() - erodeCentroidZ;
|
|
||||||
double dist = Math.sqrt(dx * dx + dz * dz);
|
double dist = Math.sqrt(dx * dx + dz * dz);
|
||||||
if (dist > erodeMaxDist) {
|
if (dist > erodeMaxDist) {
|
||||||
erodeMaxDist = dist;
|
erodeMaxDist = dist;
|
||||||
@@ -754,54 +662,29 @@ final class IrisObjectPlacementRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
VectorMap<NativeBlockState>.Cursor stiltCursor = blocks.cursor();
|
int targetLayer = ceilingHang ? topLayer : lowest;
|
||||||
while (stiltCursor.next()) {
|
for (int index = 0; index < count; index++) {
|
||||||
IrisBlockVector g = stiltCursor.key();
|
int entry = bake.paletteIndex(index);
|
||||||
NativeBlockState sourceData;
|
if (!palette.shouldStilt(entry)) {
|
||||||
try {
|
|
||||||
sourceData = stiltCursor.value();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
GenerationFailures.rethrowEngineFailure(e);
|
|
||||||
IrisLogging.reportError(e);
|
|
||||||
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (stilt cme)");
|
|
||||||
sourceData = IrisObject.States.air();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceData == null) {
|
|
||||||
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (stilt null)");
|
|
||||||
sourceData = IrisObject.States.air();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IrisObjectShaping.shouldStilt(sourceData, excludedStiltMaterials)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBlockState d = sourceData;
|
NativeBlockState d;
|
||||||
if (settings != null && settings.getPalette() != null) {
|
if (settings != null && settings.getPalette() != null) {
|
||||||
d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata);
|
d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata);
|
||||||
} else {
|
} else {
|
||||||
String mat = IrisObjectShaping.materialKey(d);
|
d = palette.stiltDefault(entry);
|
||||||
if (mat.equals("minecraft:grass_block") || mat.equals("minecraft:mycelium") || mat.equals("minecraft:podzol") || mat.equals("minecraft:dirt_path")) {
|
|
||||||
d = IrisObject.States.dirt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisBlockVector i = g.clone();
|
if (bake.layerY(index) != targetLayer)
|
||||||
spin.rotate(i);
|
|
||||||
if (ceilingHang) {
|
|
||||||
i.setY(-i.getBlockY());
|
|
||||||
}
|
|
||||||
if (translating) {
|
|
||||||
i.add(translateOffset);
|
|
||||||
}
|
|
||||||
int targetLayer = ceilingHang ? topLayer : lowest;
|
|
||||||
if (i.getBlockY() != targetLayer)
|
|
||||||
continue;
|
continue;
|
||||||
d = config.getRotation().rotate(d, spinx, spiny, spinz);
|
d = palette.rotate(d);
|
||||||
|
|
||||||
if (hasEdits) {
|
if (hasEdits) {
|
||||||
|
double exactX = exact[index * 3];
|
||||||
|
double exactY = exact[index * 3 + 1];
|
||||||
|
double exactZ = exact[index * 3 + 2];
|
||||||
for (IrisObjectReplace j : config.getEdit()) {
|
for (IrisObjectReplace j : config.getEdit()) {
|
||||||
if (rng.chance(j.getChance())) {
|
if (rng.chance(j.getChance())) {
|
||||||
for (NativeBlockState k : j.getFind(rdata)) {
|
for (NativeBlockState k : j.getFind(rdata)) {
|
||||||
@@ -809,7 +692,7 @@ final class IrisObjectPlacementRunner {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (j.isExact() ? k.matches(d) : IrisObjectShaping.materialKey(k).equals(IrisObjectShaping.materialKey(d))) {
|
if (j.isExact() ? k.matches(d) : IrisObjectShaping.materialKey(k).equals(IrisObjectShaping.materialKey(d))) {
|
||||||
NativeBlockState newData = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata);
|
NativeBlockState newData = j.getReplace(rng, exactX + x, exactY + y, exactZ + z, rdata);
|
||||||
|
|
||||||
if (IrisObjectShaping.materialKey(newData).equals(IrisObjectShaping.materialKey(d))) {
|
if (IrisObjectShaping.materialKey(newData).equals(IrisObjectShaping.materialKey(d))) {
|
||||||
d = BlockDataMergeSupport.merge(d, newData);
|
d = BlockDataMergeSupport.merge(d, newData);
|
||||||
@@ -822,15 +705,18 @@ final class IrisObjectPlacementRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d == null || !IrisObjectShaping.shouldStilt(d, excludedStiltMaterials))
|
if (d == null || !palette.shouldStiltPlaced(d))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
xx = x + (int) Math.round(i.getX());
|
xx = x + bake.offsetX(index);
|
||||||
zz = z + (int) Math.round(i.getZ());
|
zz = z + bake.offsetZ(index);
|
||||||
|
|
||||||
if (warped) {
|
if (warped) {
|
||||||
xx += surfaceWarp.fitDouble(-warpHalf, warpHalf, i.getX() + x, i.getY() + y, i.getZ() + z);
|
double exactX = exact[index * 3];
|
||||||
zz += surfaceWarp.fitDouble(-warpHalf, warpHalf, i.getZ() + z, i.getY() + y, i.getX() + x);
|
double exactY = exact[index * 3 + 1];
|
||||||
|
double exactZ = exact[index * 3 + 2];
|
||||||
|
xx += surfaceWarp.fitDouble(-warpHalf, warpHalf, exactX + x, exactY + y, exactZ + z);
|
||||||
|
zz += surfaceWarp.fitDouble(-warpHalf, warpHalf, exactZ + z, exactY + y, exactX + x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organic) {
|
if (organic) {
|
||||||
@@ -891,8 +777,6 @@ final class IrisObjectPlacementRunner {
|
|||||||
|
|
||||||
int highest = placer.getHighest(xx, zz, self.getLoader(), true);
|
int highest = placer.getHighest(xx, zz, self.getLoader(), true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int lowerBound = highest - 1;
|
int lowerBound = highest - 1;
|
||||||
if (settings != null) {
|
if (settings != null) {
|
||||||
lowerBound -= config.getStiltSettings().getOverStilt() - rng.i(0, config.getStiltSettings().getYRand());
|
lowerBound -= config.getStiltSettings().getOverStilt() - rng.i(0, config.getStiltSettings().getYRand());
|
||||||
@@ -901,8 +785,8 @@ final class IrisObjectPlacementRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (eroding) {
|
if (eroding) {
|
||||||
double dx = i.getX() - erodeCentroidX;
|
double dx = exact[index * 3] - erodeCentroidX;
|
||||||
double dz = i.getZ() - erodeCentroidZ;
|
double dz = exact[index * 3 + 2] - erodeCentroidZ;
|
||||||
double normalizedDist = Math.sqrt(dx * dx + dz * dz) / erodeMaxDist;
|
double normalizedDist = Math.sqrt(dx * dx + dz * dz) / erodeMaxDist;
|
||||||
normalizedDist = Math.min(normalizedDist, 1.0);
|
normalizedDist = Math.min(normalizedDist, 1.0);
|
||||||
int totalDepth = (lowest + y) - lowerBound;
|
int totalDepth = (lowest + y) - lowerBound;
|
||||||
@@ -933,10 +817,6 @@ final class IrisObjectPlacementRunner {
|
|||||||
}
|
}
|
||||||
placer.set(xx, j, zz, normalizePlacedWaterlogging(d, placer, xx, j, zz, waterlogCandidate));
|
placer.set(xx, j, zz, normalizePlacedWaterlogging(d, placer, xx, j, zz, waterlogCandidate));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
self.readLock.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -974,9 +854,76 @@ final class IrisObjectPlacementRunner {
|
|||||||
if (state == null || !IrisProceduralBlocks.hasProperty(state, "waterlogged")) {
|
if (state == null || !IrisProceduralBlocks.hasProperty(state, "waterlogged")) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
if (!enabled && !"true".equals(IrisProceduralBlocks.propertyValue(state, "waterlogged"))) {
|
||||||
|
return IrisProceduralBlocks.normalizeWaterlogging(state, null, false);
|
||||||
|
}
|
||||||
return IrisProceduralBlocks.normalizeWaterlogging(state, placer.get(x, y, z), enabled);
|
return IrisProceduralBlocks.normalizeWaterlogging(state, placer.get(x, y, z), enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String[] resolveMarkers(IrisObjectPlacement config, RNG rng, int x, int yv, int z, IrisData rdata,
|
||||||
|
ObjectPlacementBake bake) {
|
||||||
|
self.readLock.lock();
|
||||||
|
try {
|
||||||
|
VectorMap<NativeBlockState> blocks = self.blocks;
|
||||||
|
KMap<IrisBlockVector, String> markers = new KMap<>();
|
||||||
|
KList<IrisBlockVector> list = StreamSupport.stream(blocks.keys().spliterator(), false)
|
||||||
|
.collect(KList.collector());
|
||||||
|
// Marker selection persists into the mantle, so it must be seed-deterministic.
|
||||||
|
// Derive a side stream keyed on the placement position instead of consuming the
|
||||||
|
// caller's rng: consuming draws there would shift every later placement in the
|
||||||
|
// chunk and invalidate existing worlds/goldenhashes.
|
||||||
|
RNG markerRng = rng.nextParallelRNG(((((long) x) << 32) | (z & 0xFFFFFFFFL)) * 31L + yv);
|
||||||
|
|
||||||
|
int markerIndex = 0;
|
||||||
|
for (IrisObjectMarker j : config.getMarkers()) {
|
||||||
|
IrisMarker marker = self.getLoader().getMarkerLoader().load(j.getMarker());
|
||||||
|
int markerSalt = markerIndex++;
|
||||||
|
|
||||||
|
if (marker == null || marker.isCompatExcluded()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int max = j.getMaximumMarkers();
|
||||||
|
for (IrisBlockVector i : list.shuffleCopy(markerRng.nextParallelRNG(markerSalt))) {
|
||||||
|
if (max <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeBlockState data = blocks.get(i);
|
||||||
|
if (data == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (NativeBlockState k : j.getMark(rdata)) {
|
||||||
|
if (max <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.isExact() ? k.matches(data) : IrisObjectShaping.materialKey(k).equals(IrisObjectShaping.materialKey(data))) {
|
||||||
|
boolean a = !blocks.containsKey((IrisBlockVector) i.clone().add(new IrisBlockVector(0, 1, 0)));
|
||||||
|
boolean fff = !blocks.containsKey((IrisBlockVector) i.clone().add(new IrisBlockVector(0, 2, 0)));
|
||||||
|
|
||||||
|
if (!marker.isEmptyAbove() || (a && fff)) {
|
||||||
|
markers.put(i, j.getMarker());
|
||||||
|
max--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] resolved = new String[bake.count()];
|
||||||
|
VectorMap<NativeBlockState>.Cursor cursor = blocks.cursor();
|
||||||
|
int index = 0;
|
||||||
|
while (index < resolved.length && cursor.next()) {
|
||||||
|
resolved[index++] = markers.get(cursor.key());
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
} finally {
|
||||||
|
self.readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private KList<IrisBlockVector> transformedPaintSupport(SpinKernel spin, IrisBlockVector translateOffset,
|
private KList<IrisBlockVector> transformedPaintSupport(SpinKernel spin, IrisBlockVector translateOffset,
|
||||||
CNG warp, double half, int x, int y, int z) {
|
CNG warp, double half, int x, int y, int z) {
|
||||||
KList<IrisBlockVector> offsets = new KList<>();
|
KList<IrisBlockVector> offsets = new KList<>();
|
||||||
@@ -1337,152 +1284,159 @@ final class IrisObjectPlacementRunner {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private record StateFacts(NativeBlockState rotated, boolean waterloggable, boolean authoredWaterlogged,
|
||||||
* Precomputed vector rotation for a single placement.
|
NativeBlockState dry, boolean air) {
|
||||||
* <p>
|
private static final StateFacts ROTATED_AWAY = new StateFacts(null, false, false, null, false);
|
||||||
* This mirrors {@link IrisObjectRotation#rotate(IrisBlockVector, int, int, int)} branch for branch, with two
|
|
||||||
* differences: it mutates the vector handed to it instead of cloning, and the per-axis angle plus its cosine
|
|
||||||
* and sine are resolved once instead of once per block. The angle is a pure function of the axis clamp and the
|
|
||||||
* spin, both fixed for the placement, and Math.cos/Math.sin are pure, so every produced double is identical to
|
|
||||||
* the value the per-call form would have produced.
|
|
||||||
*/
|
|
||||||
private static final class SpinKernel {
|
|
||||||
private static final int MODE_NONE = 0;
|
|
||||||
private static final int MODE_FLIP = 1;
|
|
||||||
private static final int MODE_QUARTER = 2;
|
|
||||||
private static final int MODE_THREE_QUARTER = 3;
|
|
||||||
private static final int MODE_ANGLE = 4;
|
|
||||||
|
|
||||||
private final boolean rotates;
|
|
||||||
private final int xMode;
|
|
||||||
private final int yMode;
|
|
||||||
private final int zMode;
|
|
||||||
private final double xCos;
|
|
||||||
private final double xSin;
|
|
||||||
private final double yCos;
|
|
||||||
private final double ySin;
|
|
||||||
private final double zCos;
|
|
||||||
private final double zSin;
|
|
||||||
|
|
||||||
private SpinKernel(IrisObjectRotation rotation, int spinx, int spiny, int spinz) {
|
|
||||||
rotates = rotation.canRotate();
|
|
||||||
xMode = rotation.canRotateX() ? modeOf(rotation.getXAxis()) : MODE_NONE;
|
|
||||||
zMode = rotation.canRotateZ() ? modeOf(rotation.getZAxis()) : MODE_NONE;
|
|
||||||
yMode = rotation.canRotateY() ? modeOf(rotation.getYAxis()) : MODE_NONE;
|
|
||||||
|
|
||||||
double xAngle = xMode == MODE_ANGLE ? rotation.getXRotation(spinx) : 0;
|
|
||||||
double zAngle = zMode == MODE_ANGLE ? rotation.getZRotation(spinz) : 0;
|
|
||||||
double yAngle = yMode == MODE_ANGLE ? rotation.getYRotation(spiny) : 0;
|
|
||||||
xCos = Math.cos(xAngle);
|
|
||||||
xSin = Math.sin(xAngle);
|
|
||||||
zCos = Math.cos(zAngle);
|
|
||||||
zSin = Math.sin(zAngle);
|
|
||||||
yCos = Math.cos(yAngle);
|
|
||||||
ySin = Math.sin(yAngle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int modeOf(IrisAxisRotationClamp clamp) {
|
private static final class PlacementStates {
|
||||||
if (!clamp.isLocked()) {
|
private static final Object ROTATED_AWAY = new Object();
|
||||||
return MODE_ANGLE;
|
private static final byte UNKNOWN = 0;
|
||||||
|
private static final byte NO = 1;
|
||||||
|
private static final byte YES = 2;
|
||||||
|
|
||||||
|
private final NativeBlockState[] palette;
|
||||||
|
private final IrisObjectRotation rotation;
|
||||||
|
private final int spinx;
|
||||||
|
private final int spiny;
|
||||||
|
private final int spinz;
|
||||||
|
private final boolean preventingDecay;
|
||||||
|
private final boolean debugSmartBore;
|
||||||
|
private final Set<String> excludedStiltMaterials;
|
||||||
|
private final NativeBlockState[] decayed;
|
||||||
|
private final StateFacts[] facts;
|
||||||
|
private final byte[] stiltLayers;
|
||||||
|
private final byte[] stilts;
|
||||||
|
private final NativeBlockState[] stiltDefaults;
|
||||||
|
private IdentityHashMap<NativeBlockState, Object> rotations;
|
||||||
|
private IdentityHashMap<NativeBlockState, StateFacts> editedFacts;
|
||||||
|
private IdentityHashMap<NativeBlockState, Boolean> placedStilts;
|
||||||
|
|
||||||
|
private PlacementStates(NativeBlockState[] palette, IrisObjectRotation rotation, int spinx, int spiny, int spinz,
|
||||||
|
boolean preventingDecay, boolean debugSmartBore, Set<String> excludedStiltMaterials) {
|
||||||
|
this.palette = palette;
|
||||||
|
this.rotation = rotation;
|
||||||
|
this.spinx = spinx;
|
||||||
|
this.spiny = spiny;
|
||||||
|
this.spinz = spinz;
|
||||||
|
this.preventingDecay = preventingDecay;
|
||||||
|
this.debugSmartBore = debugSmartBore;
|
||||||
|
this.excludedStiltMaterials = excludedStiltMaterials;
|
||||||
|
this.decayed = new NativeBlockState[palette.length];
|
||||||
|
this.facts = new StateFacts[palette.length];
|
||||||
|
this.stiltLayers = new byte[palette.length];
|
||||||
|
this.stilts = new byte[palette.length];
|
||||||
|
this.stiltDefaults = new NativeBlockState[palette.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.abs(clamp.getMax()) % 360D == 180D) {
|
private NativeBlockState source(int entry) {
|
||||||
return MODE_FLIP;
|
NativeBlockState state = palette[entry];
|
||||||
|
return debugSmartBore && IrisObject.States.vair().equals(state) ? IrisObject.States.vairDebug() : state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clamp.getMax() % 360D == 90D || clamp.getMax() % 360D == -270D) {
|
private boolean stiltLayer(int entry) {
|
||||||
return MODE_QUARTER;
|
byte known = stiltLayers[entry];
|
||||||
|
if (known == UNKNOWN) {
|
||||||
|
known = IrisObjectShaping.isStiltLayerBlock(source(entry)) ? YES : NO;
|
||||||
|
stiltLayers[entry] = known;
|
||||||
|
}
|
||||||
|
return known == YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clamp.getMax() == -90D || clamp.getMax() % 360D == 270D) {
|
private NativeBlockState decayed(int entry) {
|
||||||
return MODE_THREE_QUARTER;
|
NativeBlockState resolved = decayed[entry];
|
||||||
|
if (resolved == null) {
|
||||||
|
NativeBlockState data = source(entry);
|
||||||
|
resolved = preventingDecay && IrisProceduralBlocks.hasProperty(data, "distance")
|
||||||
|
&& "false".equals(IrisProceduralBlocks.propertyValue(data, "persistent"))
|
||||||
|
? data.withProperty("persistent", "true") : data;
|
||||||
|
decayed[entry] = resolved;
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MODE_ANGLE;
|
private StateFacts facts(int entry) {
|
||||||
|
StateFacts resolved = facts[entry];
|
||||||
|
if (resolved == null) {
|
||||||
|
resolved = computeFacts(decayed(entry));
|
||||||
|
facts[entry] = resolved;
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private StateFacts factsOf(NativeBlockState state) {
|
||||||
* Rotates in place. Axis order (X, then Z, then Y) is load bearing.
|
if (editedFacts == null) {
|
||||||
*/
|
editedFacts = new IdentityHashMap<>();
|
||||||
private void rotate(IrisBlockVector v) {
|
}
|
||||||
if (!rotates) {
|
StateFacts resolved = editedFacts.get(state);
|
||||||
return;
|
if (resolved == null) {
|
||||||
|
resolved = computeFacts(state);
|
||||||
|
editedFacts.put(state, resolved);
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (xMode) {
|
private StateFacts computeFacts(NativeBlockState state) {
|
||||||
case MODE_FLIP -> {
|
NativeBlockState rotated = rotate(state);
|
||||||
v.setZ(-v.getZ());
|
if (rotated == null) {
|
||||||
v.setY(-v.getY());
|
return StateFacts.ROTATED_AWAY;
|
||||||
}
|
|
||||||
case MODE_QUARTER -> {
|
|
||||||
double z = v.getZ();
|
|
||||||
v.setZ(v.getY());
|
|
||||||
v.setY(-z);
|
|
||||||
}
|
|
||||||
case MODE_THREE_QUARTER -> {
|
|
||||||
double z = v.getZ();
|
|
||||||
v.setZ(-v.getY());
|
|
||||||
v.setY(z);
|
|
||||||
}
|
|
||||||
case MODE_ANGLE -> {
|
|
||||||
double y = xCos * v.getY() - xSin * v.getZ();
|
|
||||||
double z = xSin * v.getY() + xCos * v.getZ();
|
|
||||||
v.setY(y);
|
|
||||||
v.setZ(z);
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
}
|
}
|
||||||
|
boolean waterloggable = IrisProceduralBlocks.hasProperty(rotated, "waterlogged");
|
||||||
|
boolean authored = waterloggable && "true".equals(IrisProceduralBlocks.propertyValue(rotated, "waterlogged"));
|
||||||
|
NativeBlockState dry = waterloggable ? IrisProceduralBlocks.normalizeWaterlogging(rotated, null, false) : rotated;
|
||||||
|
String material = IrisObjectShaping.materialKey(rotated);
|
||||||
|
boolean air = material.equals("minecraft:air") || material.equals("minecraft:cave_air");
|
||||||
|
return new StateFacts(rotated, waterloggable, authored, dry, air);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (zMode) {
|
private NativeBlockState rotate(NativeBlockState state) {
|
||||||
case MODE_FLIP -> {
|
if (state == null) {
|
||||||
v.setY(-v.getY());
|
return null;
|
||||||
v.setX(-v.getX());
|
|
||||||
}
|
}
|
||||||
case MODE_QUARTER -> {
|
if (rotations == null) {
|
||||||
double y = v.getY();
|
rotations = new IdentityHashMap<>();
|
||||||
v.setY(v.getX());
|
|
||||||
v.setX(-y);
|
|
||||||
}
|
}
|
||||||
case MODE_THREE_QUARTER -> {
|
Object memo = rotations.get(state);
|
||||||
double y = v.getY();
|
if (memo == null) {
|
||||||
v.setY(-v.getX());
|
NativeBlockState rotated = rotation.rotate(state, spinx, spiny, spinz);
|
||||||
v.setX(y);
|
memo = rotated == null ? ROTATED_AWAY : rotated;
|
||||||
}
|
rotations.put(state, memo);
|
||||||
case MODE_ANGLE -> {
|
|
||||||
double x = zCos * v.getX() - zSin * v.getY();
|
|
||||||
double y = zSin * v.getX() + zCos * v.getY();
|
|
||||||
v.setX(x);
|
|
||||||
v.setY(y);
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
}
|
}
|
||||||
|
return memo == ROTATED_AWAY ? null : (NativeBlockState) memo;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (yMode) {
|
private boolean shouldStilt(int entry) {
|
||||||
case MODE_FLIP -> {
|
byte known = stilts[entry];
|
||||||
v.setX(-v.getX());
|
if (known == UNKNOWN) {
|
||||||
v.setZ(-v.getZ());
|
known = IrisObjectShaping.shouldStilt(palette[entry], excludedStiltMaterials) ? YES : NO;
|
||||||
|
stilts[entry] = known;
|
||||||
}
|
}
|
||||||
case MODE_QUARTER -> {
|
return known == YES;
|
||||||
double x = v.getX();
|
|
||||||
v.setX(v.getZ());
|
|
||||||
v.setZ(-x);
|
|
||||||
}
|
}
|
||||||
case MODE_THREE_QUARTER -> {
|
|
||||||
double x = v.getX();
|
private NativeBlockState stiltDefault(int entry) {
|
||||||
v.setX(-v.getZ());
|
NativeBlockState resolved = stiltDefaults[entry];
|
||||||
v.setZ(x);
|
if (resolved == null) {
|
||||||
|
NativeBlockState state = palette[entry];
|
||||||
|
String material = IrisObjectShaping.materialKey(state);
|
||||||
|
resolved = material.equals("minecraft:grass_block") || material.equals("minecraft:mycelium")
|
||||||
|
|| material.equals("minecraft:podzol") || material.equals("minecraft:dirt_path")
|
||||||
|
? IrisObject.States.dirt() : state;
|
||||||
|
stiltDefaults[entry] = resolved;
|
||||||
}
|
}
|
||||||
case MODE_ANGLE -> {
|
return resolved;
|
||||||
double x = yCos * v.getX() + ySin * v.getZ();
|
|
||||||
double z = -ySin * v.getX() + yCos * v.getZ();
|
|
||||||
v.setX(x);
|
|
||||||
v.setZ(z);
|
|
||||||
}
|
}
|
||||||
default -> {
|
|
||||||
|
private boolean shouldStiltPlaced(NativeBlockState state) {
|
||||||
|
if (placedStilts == null) {
|
||||||
|
placedStilts = new IdentityHashMap<>();
|
||||||
}
|
}
|
||||||
|
Boolean known = placedStilts.get(state);
|
||||||
|
if (known == null) {
|
||||||
|
known = IrisObjectShaping.shouldStilt(state, excludedStiltMaterials);
|
||||||
|
placedStilts.put(state, known);
|
||||||
}
|
}
|
||||||
|
return known;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
package art.arcane.iris.structure.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.block.TileData;
|
||||||
|
import art.arcane.iris.generation.block.VectorMap;
|
||||||
|
import art.arcane.iris.generation.decoration.tree.TreeGrounding;
|
||||||
|
import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
final class ObjectPlacementBake {
|
||||||
|
private static final int MAXIMUM_VARIANTS_PER_OBJECT = 32;
|
||||||
|
|
||||||
|
private final long blocksRevision;
|
||||||
|
private final long statesRevision;
|
||||||
|
private final TreeGrounding grounding;
|
||||||
|
private final int count;
|
||||||
|
private final int[] offsetX;
|
||||||
|
private final int[] offsetY;
|
||||||
|
private final int[] offsetZ;
|
||||||
|
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 ObjectPlacementBake(Key key, long blocksRevision, long statesRevision, TreeGrounding grounding, int count,
|
||||||
|
int[] offsetX, int[] offsetY, int[] offsetZ, int[] layerY, short[] paletteIndexes,
|
||||||
|
NativeBlockState[] palette, int[] tileBlocks, TileData[] tileValues, long[] canopy) {
|
||||||
|
this.key = key;
|
||||||
|
this.blocksRevision = blocksRevision;
|
||||||
|
this.statesRevision = statesRevision;
|
||||||
|
this.grounding = grounding;
|
||||||
|
this.count = count;
|
||||||
|
this.offsetX = offsetX;
|
||||||
|
this.offsetY = offsetY;
|
||||||
|
this.offsetZ = offsetZ;
|
||||||
|
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) {
|
||||||
|
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();
|
||||||
|
try {
|
||||||
|
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();
|
||||||
|
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) {
|
||||||
|
throw new IllegalStateException("Object " + object.getLoadKey() + " has more than "
|
||||||
|
+ Short.MAX_VALUE + " distinct block states");
|
||||||
|
}
|
||||||
|
short[] paletteIndexes = new short[index];
|
||||||
|
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 {
|
||||||
|
object.readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void transform(IrisBlockVector source, IrisBlockVector target, SpinKernel spin, boolean ceilingHang,
|
||||||
|
IrisBlockVector translateOffset) {
|
||||||
|
target.setX(source.getX());
|
||||||
|
target.setY(source.getY());
|
||||||
|
target.setZ(source.getZ());
|
||||||
|
spin.rotate(target);
|
||||||
|
if (ceilingHang) {
|
||||||
|
target.setY(-target.getBlockY());
|
||||||
|
}
|
||||||
|
if (translateOffset != null) {
|
||||||
|
target.add(translateOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean matches(IrisObject object) {
|
||||||
|
return object.blocks.modificationRevision() == blocksRevision
|
||||||
|
&& object.states.modificationRevision() == statesRevision
|
||||||
|
&& object.getTreeGrounding() == grounding;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int offsetX(int index) {
|
||||||
|
return offsetX[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int offsetY(int index) {
|
||||||
|
return offsetY[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int offsetZ(int index) {
|
||||||
|
return offsetZ[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int layerY(int index) {
|
||||||
|
return layerY == null ? offsetY[index] : layerY[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int paletteIndex(int index) {
|
||||||
|
return paletteIndexes[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeBlockState[] palette() {
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] tileBlocks() {
|
||||||
|
return tileBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
TileData[] tileValues() {
|
||||||
|
return tileValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canopy(int index) {
|
||||||
|
return canopy != null && (canopy[index >>> 6] & (1L << (index & 63))) != 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
double[] exact(IrisObject object, SpinKernel spin) {
|
||||||
|
double[] resolved = exact;
|
||||||
|
if (resolved != null) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
object.readLock.lock();
|
||||||
|
try {
|
||||||
|
resolved = new double[count * 3];
|
||||||
|
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, key.ceilingHang(), key.translateOffset());
|
||||||
|
resolved[index * 3] = rotated.getX();
|
||||||
|
resolved[index * 3 + 1] = rotated.getY();
|
||||||
|
resolved[index * 3 + 2] = rotated.getZ();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (index != count) {
|
||||||
|
throw new IllegalStateException("Object " + object.getLoadKey() + " changed while its placement bake was read");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
object.readLock.unlock();
|
||||||
|
}
|
||||||
|
exact = resolved;
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 translateZ) {
|
||||||
|
static Key of(SpinKernel spin, boolean ceilingHang, IrisBlockVector translateOffset) {
|
||||||
|
boolean translating = translateOffset != null;
|
||||||
|
return new Key(spin.rotates, spin.xMode, spin.yMode, spin.zMode,
|
||||||
|
Double.doubleToLongBits(spin.xCos), Double.doubleToLongBits(spin.xSin),
|
||||||
|
Double.doubleToLongBits(spin.yCos), Double.doubleToLongBits(spin.ySin),
|
||||||
|
Double.doubleToLongBits(spin.zCos), Double.doubleToLongBits(spin.zSin), ceilingHang, translating,
|
||||||
|
translating ? Double.doubleToLongBits(translateOffset.getX()) : 0L,
|
||||||
|
translating ? Double.doubleToLongBits(translateOffset.getY()) : 0L,
|
||||||
|
translating ? Double.doubleToLongBits(translateOffset.getZ()) : 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisBlockVector translateOffset() {
|
||||||
|
return translating ? new IrisBlockVector(Double.longBitsToDouble(translateX),
|
||||||
|
Double.longBitsToDouble(translateY), Double.longBitsToDouble(translateZ)) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package art.arcane.iris.structure.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Precomputed vector rotation for a single placement.
|
||||||
|
* <p>
|
||||||
|
* This mirrors {@link IrisObjectRotation#rotate(IrisBlockVector, int, int, int)} branch for branch, with two
|
||||||
|
* differences: it mutates the vector handed to it instead of cloning, and the per-axis angle plus its cosine
|
||||||
|
* and sine are resolved once instead of once per block. The angle is a pure function of the axis clamp and the
|
||||||
|
* spin, both fixed for the placement, and Math.cos/Math.sin are pure, so every produced double is identical to
|
||||||
|
* the value the per-call form would have produced.
|
||||||
|
*/
|
||||||
|
final class SpinKernel {
|
||||||
|
static final int MODE_NONE = 0;
|
||||||
|
static final int MODE_FLIP = 1;
|
||||||
|
static final int MODE_QUARTER = 2;
|
||||||
|
static final int MODE_THREE_QUARTER = 3;
|
||||||
|
static final int MODE_ANGLE = 4;
|
||||||
|
|
||||||
|
final boolean rotates;
|
||||||
|
final int xMode;
|
||||||
|
final int yMode;
|
||||||
|
final int zMode;
|
||||||
|
final double xCos;
|
||||||
|
final double xSin;
|
||||||
|
final double yCos;
|
||||||
|
final double ySin;
|
||||||
|
final double zCos;
|
||||||
|
final double zSin;
|
||||||
|
|
||||||
|
SpinKernel(IrisObjectRotation rotation, int spinx, int spiny, int spinz) {
|
||||||
|
rotates = rotation.canRotate();
|
||||||
|
xMode = rotation.canRotateX() ? modeOf(rotation.getXAxis()) : MODE_NONE;
|
||||||
|
zMode = rotation.canRotateZ() ? modeOf(rotation.getZAxis()) : MODE_NONE;
|
||||||
|
yMode = rotation.canRotateY() ? modeOf(rotation.getYAxis()) : MODE_NONE;
|
||||||
|
|
||||||
|
double xAngle = xMode == MODE_ANGLE ? rotation.getXRotation(spinx) : 0;
|
||||||
|
double zAngle = zMode == MODE_ANGLE ? rotation.getZRotation(spinz) : 0;
|
||||||
|
double yAngle = yMode == MODE_ANGLE ? rotation.getYRotation(spiny) : 0;
|
||||||
|
xCos = Math.cos(xAngle);
|
||||||
|
xSin = Math.sin(xAngle);
|
||||||
|
zCos = Math.cos(zAngle);
|
||||||
|
zSin = Math.sin(zAngle);
|
||||||
|
yCos = Math.cos(yAngle);
|
||||||
|
ySin = Math.sin(yAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int modeOf(IrisAxisRotationClamp clamp) {
|
||||||
|
if (!clamp.isLocked()) {
|
||||||
|
return MODE_ANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(clamp.getMax()) % 360D == 180D) {
|
||||||
|
return MODE_FLIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clamp.getMax() % 360D == 90D || clamp.getMax() % 360D == -270D) {
|
||||||
|
return MODE_QUARTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clamp.getMax() == -90D || clamp.getMax() % 360D == 270D) {
|
||||||
|
return MODE_THREE_QUARTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MODE_ANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates in place. Axis order (X, then Z, then Y) is load bearing.
|
||||||
|
*/
|
||||||
|
void rotate(IrisBlockVector v) {
|
||||||
|
if (!rotates) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (xMode) {
|
||||||
|
case MODE_FLIP -> {
|
||||||
|
v.setZ(-v.getZ());
|
||||||
|
v.setY(-v.getY());
|
||||||
|
}
|
||||||
|
case MODE_QUARTER -> {
|
||||||
|
double z = v.getZ();
|
||||||
|
v.setZ(v.getY());
|
||||||
|
v.setY(-z);
|
||||||
|
}
|
||||||
|
case MODE_THREE_QUARTER -> {
|
||||||
|
double z = v.getZ();
|
||||||
|
v.setZ(-v.getY());
|
||||||
|
v.setY(z);
|
||||||
|
}
|
||||||
|
case MODE_ANGLE -> {
|
||||||
|
double y = xCos * v.getY() - xSin * v.getZ();
|
||||||
|
double z = xSin * v.getY() + xCos * v.getZ();
|
||||||
|
v.setY(y);
|
||||||
|
v.setZ(z);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (zMode) {
|
||||||
|
case MODE_FLIP -> {
|
||||||
|
v.setY(-v.getY());
|
||||||
|
v.setX(-v.getX());
|
||||||
|
}
|
||||||
|
case MODE_QUARTER -> {
|
||||||
|
double y = v.getY();
|
||||||
|
v.setY(v.getX());
|
||||||
|
v.setX(-y);
|
||||||
|
}
|
||||||
|
case MODE_THREE_QUARTER -> {
|
||||||
|
double y = v.getY();
|
||||||
|
v.setY(-v.getX());
|
||||||
|
v.setX(y);
|
||||||
|
}
|
||||||
|
case MODE_ANGLE -> {
|
||||||
|
double x = zCos * v.getX() - zSin * v.getY();
|
||||||
|
double y = zSin * v.getX() + zCos * v.getY();
|
||||||
|
v.setX(x);
|
||||||
|
v.setY(y);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (yMode) {
|
||||||
|
case MODE_FLIP -> {
|
||||||
|
v.setX(-v.getX());
|
||||||
|
v.setZ(-v.getZ());
|
||||||
|
}
|
||||||
|
case MODE_QUARTER -> {
|
||||||
|
double x = v.getX();
|
||||||
|
v.setX(v.getZ());
|
||||||
|
v.setZ(-x);
|
||||||
|
}
|
||||||
|
case MODE_THREE_QUARTER -> {
|
||||||
|
double x = v.getX();
|
||||||
|
v.setX(-v.getZ());
|
||||||
|
v.setZ(x);
|
||||||
|
}
|
||||||
|
case MODE_ANGLE -> {
|
||||||
|
double x = yCos * v.getX() + ySin * v.getZ();
|
||||||
|
double z = -ySin * v.getX() + yCos * v.getZ();
|
||||||
|
v.setX(x);
|
||||||
|
v.setZ(z);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -791,7 +791,7 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
|||||||
|
|
||||||
object.place(0, ANCHOR_Y, 0, placer, placement, new RNG(2L), data);
|
object.place(0, ANCHOR_Y, 0, placer, placement, new RNG(2L), data);
|
||||||
|
|
||||||
assertEquals(1, placer.blockReads);
|
assertEquals(enabled || authored ? 1 : 0, placer.blockReads);
|
||||||
assertEquals(1, placer.writes().size());
|
assertEquals(1, placer.writes().size());
|
||||||
assertSame(submerged && (enabled || authored) ? wet : dry,
|
assertSame(submerged && (enabled || authored) ? wet : dry,
|
||||||
placer.writes().get(0).state());
|
placer.writes().get(0).state());
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package art.arcane.iris.structure.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||||
|
import art.arcane.iris.testsupport.KeyedBlockState;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotSame;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
public class ObjectPlacementBakeTest {
|
||||||
|
@Test
|
||||||
|
public void spinsResolvingToTheSameKernelShareOneBake() {
|
||||||
|
IrisObject object = object();
|
||||||
|
IrisObjectRotation rotation = new IrisObjectRotation();
|
||||||
|
|
||||||
|
ObjectPlacementBake first = ObjectPlacementBake.of(object, new SpinKernel(rotation, 0, 10, 0), false, null);
|
||||||
|
ObjectPlacementBake second = ObjectPlacementBake.of(object, new SpinKernel(rotation, 3, 80, 9), false, null);
|
||||||
|
ObjectPlacementBake turned = ObjectPlacementBake.of(object, new SpinKernel(rotation, 0, 100, 0), false, null);
|
||||||
|
|
||||||
|
assertSame(first, second);
|
||||||
|
assertNotSame(first, turned);
|
||||||
|
assertEquals(3, first.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bakedOffsetsMatchTheKernelTransformInCursorOrder() {
|
||||||
|
IrisObject object = object();
|
||||||
|
IrisObjectRotation rotation = IrisObjectRotation.of(0D, 90D, 0D);
|
||||||
|
SpinKernel spin = new SpinKernel(rotation, 0, 0, 0);
|
||||||
|
IrisBlockVector translate = new IrisBlockVector(1.5D, -2D, 0.25D);
|
||||||
|
|
||||||
|
ObjectPlacementBake bake = ObjectPlacementBake.of(object, spin, true, translate);
|
||||||
|
double[] exact = bake.exact(object, spin);
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (IrisBlockVector source : object.getBlocks().keys()) {
|
||||||
|
IrisBlockVector expected = source.clone();
|
||||||
|
spin.rotate(expected);
|
||||||
|
expected.setY(-expected.getBlockY());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void editedObjectsRebake() {
|
||||||
|
IrisObject object = object();
|
||||||
|
SpinKernel spin = new SpinKernel(new IrisObjectRotation(), 0, 0, 0);
|
||||||
|
ObjectPlacementBake before = ObjectPlacementBake.of(object, spin, false, null);
|
||||||
|
|
||||||
|
object.getBlocks().put(new IrisBlockVector(1, 1, 1), new KeyedBlockState("minecraft:dirt"));
|
||||||
|
ObjectPlacementBake after = ObjectPlacementBake.of(object, spin, false, null);
|
||||||
|
|
||||||
|
assertNotSame(before, after);
|
||||||
|
assertEquals(4, after.count());
|
||||||
|
assertSame(after, ObjectPlacementBake.of(object, spin, false, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisObject object() {
|
||||||
|
IrisObject object = new IrisObject(5, 5, 5);
|
||||||
|
KeyedBlockState stone = new KeyedBlockState("minecraft:stone");
|
||||||
|
object.getBlocks().put(new IrisBlockVector(-2, 0, 1), stone);
|
||||||
|
object.getBlocks().put(new IrisBlockVector(0, -1, 2), stone);
|
||||||
|
object.getBlocks().put(new IrisBlockVector(2, 2, -2), new KeyedBlockState("minecraft:oak_log[axis=y]"));
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user