mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Fixed object transform stability
- Placement skips blocks omitted by unsupported rotations - Rotated object copies omit matching tile data - Scaling captures geometry and volume under one read lock - Regression tests cover omitted orientations and concurrent scaling
This commit is contained in:
@@ -533,6 +533,9 @@ final class IrisObjectPlacementRunner {
|
||||
}
|
||||
|
||||
data = config.getRotation().rotate(data, spinx, spiny, spinz);
|
||||
if (data == null) {
|
||||
continue;
|
||||
}
|
||||
xx = x + (int) Math.round(i.getX());
|
||||
|
||||
int yy = y + (int) Math.round(i.getY());
|
||||
|
||||
@@ -30,10 +30,12 @@ import art.arcane.iris.util.project.interpolation.Interpolation3D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Geometric transforms for {@link IrisObject}: rotation, scaling and the interpolated upscalers.
|
||||
@@ -52,15 +54,23 @@ final class IrisObjectTransforms {
|
||||
self.writeLock.lock();
|
||||
try {
|
||||
VectorMap<PlatformBlockState> d = new VectorMap<>();
|
||||
Set<IrisBlockVector> omitted = new HashSet<>();
|
||||
|
||||
for (var entry : self.blocks) {
|
||||
d.put(r.rotate(entry.getKey(), spinx, spiny, spinz), r.rotate(entry.getValue(), spinx, spiny, spinz));
|
||||
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : self.blocks) {
|
||||
PlatformBlockState rotated = r.rotate(entry.getValue(), spinx, spiny, spinz);
|
||||
if (rotated == null) {
|
||||
omitted.add(entry.getKey());
|
||||
continue;
|
||||
}
|
||||
d.put(r.rotate(entry.getKey(), spinx, spiny, spinz), rotated);
|
||||
}
|
||||
|
||||
VectorMap<TileData> dx = new VectorMap<>();
|
||||
|
||||
for (var entry : self.states) {
|
||||
dx.put(r.rotate(entry.getKey(), spinx, spiny, spinz), entry.getValue());
|
||||
for (Map.Entry<IrisBlockVector, TileData> entry : self.states) {
|
||||
if (!omitted.contains(entry.getKey())) {
|
||||
dx.put(r.rotate(entry.getKey(), spinx, spiny, spinz), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
self.blocks = d;
|
||||
@@ -100,26 +110,27 @@ final class IrisObjectTransforms {
|
||||
VectorMap<TileData> placeTile = new VectorMap<>();
|
||||
VectorMap<IrisBlockVector> placeMax = savedOrigin && scale > 1 ? new VectorMap<>() : null;
|
||||
|
||||
IrisVector center = new IrisVector(self.getCenter().getX(), self.getCenter().getY(), self.getCenter().getZ());
|
||||
if (self.getH() == 2) {
|
||||
center = center.setY(center.getBlockY() + 0.5);
|
||||
}
|
||||
if (self.getW() == 2) {
|
||||
center = center.setX(center.getBlockX() + 0.5);
|
||||
}
|
||||
if (self.getD() == 2) {
|
||||
center = center.setZ(center.getBlockZ() + 0.5);
|
||||
}
|
||||
|
||||
IrisObject oo = savedOrigin ? createOriginScaledObject(self, scale)
|
||||
: new IrisObject((int) Math.ceil((self.w * scale) + (scale * 2)), (int) Math.ceil((self.h * scale) + (scale * 2)), (int) Math.ceil((self.d * scale) + (scale * 2)));
|
||||
oo.setLoadKey(self.getLoadKey());
|
||||
oo.setLoader(self.getLoader());
|
||||
oo.setLoadFile(self.getLoadFile());
|
||||
|
||||
IrisVector center;
|
||||
IrisObject oo;
|
||||
boolean hasTiles;
|
||||
self.readLock.lock();
|
||||
try {
|
||||
center = new IrisVector(self.getCenter().getX(), self.getCenter().getY(), self.getCenter().getZ());
|
||||
if (self.getH() == 2) {
|
||||
center = center.setY(center.getBlockY() + 0.5);
|
||||
}
|
||||
if (self.getW() == 2) {
|
||||
center = center.setX(center.getBlockX() + 0.5);
|
||||
}
|
||||
if (self.getD() == 2) {
|
||||
center = center.setZ(center.getBlockZ() + 0.5);
|
||||
}
|
||||
|
||||
oo = savedOrigin ? createOriginScaledObject(self, scale)
|
||||
: new IrisObject((int) Math.ceil((self.w * scale) + (scale * 2)), (int) Math.ceil((self.h * scale) + (scale * 2)), (int) Math.ceil((self.d * scale) + (scale * 2)));
|
||||
oo.setLoadKey(self.getLoadKey());
|
||||
oo.setLoader(self.getLoader());
|
||||
oo.setLoadFile(self.getLoadFile());
|
||||
hasTiles = !self.states.isEmpty();
|
||||
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : self.blocks) {
|
||||
PlatformBlockState bd = entry.getValue();
|
||||
|
||||
+34
@@ -10,6 +10,9 @@ import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.Directional;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -19,6 +22,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -69,6 +73,36 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedRotatedOrientationOmitsBlockTileAndListener() {
|
||||
PlatformBlockState torch = state("minecraft:wall_torch", false);
|
||||
Directional directional = mock(Directional.class);
|
||||
Material material = mock(Material.class);
|
||||
when(torch.nativeHandle()).thenReturn(directional);
|
||||
when(directional.clone()).thenReturn(directional);
|
||||
when(directional.getFacing()).thenReturn(BlockFace.NORTH);
|
||||
when(directional.getFaces()).thenReturn(Set.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST));
|
||||
when(directional.getMaterial()).thenReturn(material);
|
||||
IrisObjectPlacement placement = placement();
|
||||
placement.setRotation(IrisObjectRotation.of(90, 0, 0));
|
||||
assertNull(placement.getRotation().rotate(torch, 0, 0, 0));
|
||||
IrisObject object = lineObject(3);
|
||||
object.setUnsigned(1, 0, 0, torch);
|
||||
object.setUnsignedTile(1, 0, 0, new TileData("minecraft:wall_torch", new KMap<>()));
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
List<PlatformBlockState> placed = new ArrayList<>();
|
||||
|
||||
int result = object.place(0, ANCHOR_Y, 0, placer, placement, new RNG(2L),
|
||||
(position, state) -> placed.add(state), null, data);
|
||||
|
||||
assertEquals(ANCHOR_Y, result);
|
||||
assertEquals(2, placer.writes().size());
|
||||
assertEquals(List.of(solid, solid), placed);
|
||||
assertNull(placer.get(0, ANCHOR_Y, 0));
|
||||
assertNull(placer.getData(0, ANCHOR_Y, 0, TileData.class));
|
||||
assertEquals(1, object.getStates().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void placementFailurePropagatesToTheCaller() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
|
||||
@@ -16,6 +16,9 @@ import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -46,6 +49,40 @@ public class IrisObjectTransformsTileTest {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rotationOmitsUnsupportedBlocksAndTheirTiles() {
|
||||
IrisObject source = new IrisObject(3, 1, 1);
|
||||
PlatformBlockState unsupported = state("minecraft:wall_torch");
|
||||
source.setUnsigned(0, 0, 0, unsupported);
|
||||
source.setUnsignedTile(0, 0, 0, tile("minecraft:wall_torch", "omitted"));
|
||||
source.setUnsigned(1, 0, 0, state("minecraft:chest"));
|
||||
source.setUnsignedTile(1, 0, 0, tile("minecraft:chest", "kept"));
|
||||
source.setUnsigned(2, 0, 0, state("minecraft:stone"));
|
||||
IrisObjectRotation.StateRotator previous = IrisObjectRotation.bindPlatformRotator(
|
||||
(rotation, block, x, y, z) -> block == unsupported ? null : block);
|
||||
IrisObject rotated;
|
||||
try {
|
||||
rotated = source.rotateCopy(IrisObjectRotation.of(0, 90, 0));
|
||||
} finally {
|
||||
IrisObjectRotation.restorePlatformRotator(previous);
|
||||
}
|
||||
|
||||
assertEquals(2, rotated.getBlocks().size());
|
||||
assertEquals(1, rotated.getStates().size());
|
||||
for (Map.Entry<IrisBlockVector, TileData> entry : rotated.getStates()) {
|
||||
assertEquals("minecraft:chest", rotated.getBlocks().get(entry.getKey()).materialKey());
|
||||
assertEquals("kept", entry.getValue().getProperties().get("name"));
|
||||
}
|
||||
assertEquals(3, source.getBlocks().size());
|
||||
assertEquals(2, source.getStates().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scalingWaitsForMatchingGeometryAndVolume() throws Exception {
|
||||
assertConsistentScaling(false);
|
||||
assertConsistentScaling(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enlargedTilesFollowEveryExpandedBlockAndCloneProperties() {
|
||||
IrisObject source = new IrisObject(1, 1, 1);
|
||||
@@ -186,6 +223,52 @@ public class IrisObjectTransformsTileTest {
|
||||
assertEquals(8, scaled.getStates().size());
|
||||
}
|
||||
|
||||
private static void assertConsistentScaling(boolean savedOrigin) throws Exception {
|
||||
IrisObject source = new IrisObject(9, 5, 3);
|
||||
source.setUnsigned(0, 0, 0, state("minecraft:stone"));
|
||||
FutureTask<IrisObject> scaling = new FutureTask<>(() -> savedOrigin
|
||||
? source.scaledAroundOrigin(2, IrisObjectPlacementScaleInterpolator.NONE)
|
||||
: source.scaled(2, IrisObjectPlacementScaleInterpolator.NONE));
|
||||
Thread worker = new Thread(scaling, "object-scaling-snapshot");
|
||||
source.writeLock.lock();
|
||||
try {
|
||||
worker.start();
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||
while (worker.getState() != Thread.State.WAITING && System.nanoTime() < deadline) {
|
||||
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
|
||||
}
|
||||
assertEquals(Thread.State.WAITING, worker.getState());
|
||||
source.setW(2);
|
||||
source.setH(2);
|
||||
source.setD(2);
|
||||
source.setCenter(new Vector3i(1, 1, 1));
|
||||
source.getBlocks().clear();
|
||||
source.setUnsigned(0, 0, 0, state("minecraft:chest"));
|
||||
source.setUnsignedTile(0, 0, 0, tile("minecraft:chest", "new volume"));
|
||||
} finally {
|
||||
source.writeLock.unlock();
|
||||
}
|
||||
try {
|
||||
IrisObject actual = scaling.get(5, TimeUnit.SECONDS);
|
||||
IrisObject expected = savedOrigin
|
||||
? source.scaledAroundOrigin(2, IrisObjectPlacementScaleInterpolator.NONE)
|
||||
: source.scaled(2, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
assertEquals(expected.getCenter(), actual.getCenter());
|
||||
assertEquals(expected.getW(), actual.getW());
|
||||
assertEquals(expected.getH(), actual.getH());
|
||||
assertEquals(expected.getD(), actual.getD());
|
||||
assertEquals(expected.getBlocks().size(), actual.getBlocks().size());
|
||||
assertEquals(expected.getStates().size(), actual.getStates().size());
|
||||
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : expected.getBlocks()) {
|
||||
assertEquals(entry.getValue(), actual.getBlocks().get(entry.getKey()));
|
||||
assertEquals(expected.getStates().get(entry.getKey()), actual.getStates().get(entry.getKey()));
|
||||
}
|
||||
} finally {
|
||||
worker.interrupt();
|
||||
worker.join(TimeUnit.SECONDS.toMillis(5));
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisObject asymmetricObject() {
|
||||
IrisObject source = new IrisObject(7, 5, 3);
|
||||
source.setCenter(new Vector3i(5, 1, 2));
|
||||
|
||||
Reference in New Issue
Block a user