mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Merged object placement improvements
- Native structure protection uses transformed object bounds - Object copies share the volume mutation lock - Upscaling writes expanded voxels without temporary lists
This commit is contained in:
commit
80155cef93
6 files changed
+150
-53
No files matched your search
@@ -110,7 +110,7 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
this.d = d;
|
this.d = d;
|
||||||
center = new Vector3i(w / 2, h / 2, d / 2);
|
center = new Vector3i(w / 2, h / 2, d / 2);
|
||||||
shrinkOffset = new Vector3i(0, 0, 0);
|
shrinkOffset = new Vector3i(0, 0, 0);
|
||||||
var lock = new ReentrantReadWriteLock();
|
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
readLock = lock.readLock();
|
readLock = lock.readLock();
|
||||||
writeLock = lock.writeLock();
|
writeLock = lock.writeLock();
|
||||||
}
|
}
|
||||||
@@ -135,17 +135,22 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
return aabb.aquire(() -> getAABBFor(new IrisBlockVector(w, h, d)));
|
return aabb.aquire(() -> getAABBFor(new IrisBlockVector(w, h, d)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized IrisObject copy() {
|
public IrisObject copy() {
|
||||||
IrisObject o = new IrisObject(w, h, d);
|
readLock.lock();
|
||||||
o.setLoadKey(getLoadKey());
|
try {
|
||||||
o.setLoader(getLoader());
|
IrisObject copy = new IrisObject(w, h, d);
|
||||||
o.setLoadFile(getLoadFile());
|
copy.setLoadKey(getLoadKey());
|
||||||
o.setCenter(getCenter().clone());
|
copy.setLoader(getLoader());
|
||||||
|
copy.setLoadFile(getLoadFile());
|
||||||
|
copy.setCenter(getCenter().clone());
|
||||||
|
|
||||||
blocks.forEach((i, v) -> o.blocks.put(i.clone(), v));
|
blocks.forEach(copy.blocks::put);
|
||||||
states.forEach((i, v) -> o.states.put(i.clone(), v.clone()));
|
states.forEach((position, tile) -> copy.states.put(position, tile.clone()));
|
||||||
|
|
||||||
return o;
|
return copy;
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readLegacy(InputStream in) throws IOException {
|
public void readLegacy(InputStream in) throws IOException {
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ final class IrisObjectPlacementRunner {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rawStructurePiece && nativeStructureVetoes(placer, config, spin, translating, translateOffset, ceilingHang,
|
if (!rawStructurePiece && nativeStructureVetoes(placer, config, spin, placementBounds, translating, translateOffset, ceilingHang,
|
||||||
yv < 0 && config.getMode() == ObjectPlaceMode.PAINT, warpMargin, x, y + yrand, z)) {
|
yv < 0 && config.getMode() == ObjectPlaceMode.PAINT, warpMargin, x, y + yrand, z)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -955,7 +955,7 @@ final class IrisObjectPlacementRunner {
|
|||||||
* near this placement means no per block work at all. Only when the placement envelope meets a piece does the
|
* near this placement means no per block work at all. Only when the placement envelope meets a piece does the
|
||||||
* precise pass run, and the first solid block inside a piece rejects the whole object before any write.
|
* precise pass run, and the first solid block inside a piece rejects the whole object before any write.
|
||||||
*/
|
*/
|
||||||
private boolean nativeStructureVetoes(IObjectPlacer placer, IrisObjectPlacement config, SpinKernel spin,
|
private boolean nativeStructureVetoes(IObjectPlacer placer, IrisObjectPlacement config, SpinKernel spin, TransformedBounds bounds,
|
||||||
boolean translating, IrisBlockVector translateOffset, boolean ceilingHang,
|
boolean translating, IrisBlockVector translateOffset, boolean ceilingHang,
|
||||||
boolean paint, int warpMargin, int x, int y, int z) {
|
boolean paint, int warpMargin, int x, int y, int z) {
|
||||||
Engine engine = placer.getEngine();
|
Engine engine = placer.getEngine();
|
||||||
@@ -963,23 +963,21 @@ final class IrisObjectPlacementRunner {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int margin = (Math.max(self.getW(), Math.max(self.getH(), self.getD())) / 2) + 1 + warpMargin;
|
int minX = x + bounds.minX();
|
||||||
if (translating) {
|
int maxX = x + bounds.maxX();
|
||||||
margin += Math.max(Math.abs(translateOffset.getBlockX()),
|
int minZ = z + bounds.minZ();
|
||||||
Math.max(Math.abs(translateOffset.getBlockY()), Math.abs(translateOffset.getBlockZ())));
|
int maxZ = z + bounds.maxZ();
|
||||||
}
|
KList<NativeStructureVolume> volumes = engine.getNativeStructureVolumes(minX, minZ, maxX, maxZ);
|
||||||
|
|
||||||
KList<NativeStructureVolume> volumes = engine.getNativeStructureVolumes(x - margin, z - margin, x + margin, z + margin);
|
|
||||||
if (volumes == null || volumes.isEmpty()) {
|
if (volumes == null || volumes.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int worldY = y + engine.getMinHeight();
|
int worldY = y + engine.getMinHeight();
|
||||||
int envelopeMinY = paint ? Integer.MIN_VALUE : worldY - margin;
|
int envelopeMinY = paint ? Integer.MIN_VALUE : worldY + bounds.minY() - warpMargin;
|
||||||
int envelopeMaxY = paint ? Integer.MAX_VALUE : worldY + margin;
|
int envelopeMaxY = paint ? Integer.MAX_VALUE : worldY + bounds.maxY() + warpMargin;
|
||||||
boolean envelopeMeetsPiece = false;
|
boolean envelopeMeetsPiece = false;
|
||||||
for (NativeStructureVolume volume : volumes) {
|
for (NativeStructureVolume volume : volumes) {
|
||||||
if (volume.intersects(x - margin, envelopeMinY, z - margin, x + margin, envelopeMaxY, z + margin)) {
|
if (volume.intersects(minX, envelopeMinY, minZ, maxX, envelopeMaxY, maxZ)) {
|
||||||
envelopeMeetsPiece = true;
|
envelopeMeetsPiece = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,12 @@ import art.arcane.iris.spi.IrisLogging;
|
|||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import art.arcane.iris.util.common.data.VectorMap;
|
import art.arcane.iris.util.common.data.VectorMap;
|
||||||
import art.arcane.iris.util.common.math.IrisBlockVector;
|
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||||
import art.arcane.iris.util.common.math.IrisVector;
|
|
||||||
import art.arcane.iris.util.common.math.Vector3i;
|
import art.arcane.iris.util.common.math.Vector3i;
|
||||||
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
||||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||||
import art.arcane.volmlib.util.format.Form;
|
import art.arcane.volmlib.util.format.Form;
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -289,23 +286,4 @@ final class IrisObjectShaping {
|
|||||||
static String materialKey(PlatformBlockState state) {
|
static String materialKey(PlatformBlockState state) {
|
||||||
return IrisProceduralBlocks.materialKey(state);
|
return IrisProceduralBlocks.materialKey(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<IrisBlockVector> blocksBetweenTwoPoints(IrisVector loc1, IrisVector loc2) {
|
|
||||||
List<IrisBlockVector> locations = new ArrayList<>();
|
|
||||||
int topBlockX = Math.max(loc1.getBlockX(), loc2.getBlockX());
|
|
||||||
int bottomBlockX = Math.min(loc1.getBlockX(), loc2.getBlockX());
|
|
||||||
int topBlockY = Math.max(loc1.getBlockY(), loc2.getBlockY());
|
|
||||||
int bottomBlockY = Math.min(loc1.getBlockY(), loc2.getBlockY());
|
|
||||||
int topBlockZ = Math.max(loc1.getBlockZ(), loc2.getBlockZ());
|
|
||||||
int bottomBlockZ = Math.min(loc1.getBlockZ(), loc2.getBlockZ());
|
|
||||||
|
|
||||||
for (int x = bottomBlockX; x <= topBlockX; x++) {
|
|
||||||
for (int z = bottomBlockZ; z <= topBlockZ; z++) {
|
|
||||||
for (int y = bottomBlockY; y <= topBlockY; y++) {
|
|
||||||
locations.add(new IrisBlockVector(x, y, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return locations;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -96,8 +96,6 @@ final class IrisObjectTransforms {
|
|||||||
scale = scale - 0.0001;
|
scale = scale - 0.0001;
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisPosition l1 = self.getAABB().max();
|
|
||||||
IrisPosition l2 = self.getAABB().min();
|
|
||||||
VectorMap<PlatformBlockState> placeBlock = new VectorMap<>();
|
VectorMap<PlatformBlockState> placeBlock = new VectorMap<>();
|
||||||
VectorMap<TileData> placeTile = new VectorMap<>();
|
VectorMap<TileData> placeTile = new VectorMap<>();
|
||||||
VectorMap<IrisBlockVector> placeMax = savedOrigin && scale > 1 ? new VectorMap<>() : null;
|
VectorMap<IrisBlockVector> placeMax = savedOrigin && scale > 1 ? new VectorMap<>() : null;
|
||||||
@@ -153,13 +151,27 @@ final class IrisObjectTransforms {
|
|||||||
if (scale > 1) {
|
if (scale > 1) {
|
||||||
IrisVector minimum = savedOrigin ? v : v.clone().add(center);
|
IrisVector minimum = savedOrigin ? v : v.clone().add(center);
|
||||||
IrisVector maximum = savedOrigin ? placeMax.get(v) : v.clone().add(center).add(sm1);
|
IrisVector maximum = savedOrigin ? placeMax.get(v) : v.clone().add(center).add(sm1);
|
||||||
for (IrisBlockVector vec : IrisObjectShaping.blocksBetweenTwoPoints(minimum, maximum)) {
|
int minX = Math.min(minimum.getBlockX(), maximum.getBlockX());
|
||||||
oo.blocks.put(vec, entry.getValue());
|
int maxX = Math.max(minimum.getBlockX(), maximum.getBlockX());
|
||||||
if (hasTiles) {
|
int minY = Math.min(minimum.getBlockY(), maximum.getBlockY());
|
||||||
if (tile == null) {
|
int maxY = Math.max(minimum.getBlockY(), maximum.getBlockY());
|
||||||
oo.states.remove(vec);
|
int minZ = Math.min(minimum.getBlockZ(), maximum.getBlockZ());
|
||||||
} else {
|
int maxZ = Math.max(minimum.getBlockZ(), maximum.getBlockZ());
|
||||||
oo.states.put(vec, tile.clone());
|
IrisBlockVector position = new IrisBlockVector(minX, minY, minZ);
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
position.setX(x);
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
position.setZ(z);
|
||||||
|
for (int y = minY; y <= maxY; y++) {
|
||||||
|
position.setY(y);
|
||||||
|
oo.blocks.put(position, entry.getValue());
|
||||||
|
if (hasTiles) {
|
||||||
|
if (tile == null) {
|
||||||
|
oo.states.remove(position);
|
||||||
|
} else {
|
||||||
|
oo.states.put(position, tile.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package art.arcane.iris.engine.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||||
|
import art.arcane.iris.util.common.math.Vector3i;
|
||||||
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotSame;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class IrisObjectCopyTest {
|
||||||
|
@Test
|
||||||
|
public void copyWaitsForVolumeMutationToComplete() throws Exception {
|
||||||
|
IrisObject source = new IrisObject(1, 1, 1);
|
||||||
|
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||||
|
CountDownLatch started = new CountDownLatch(1);
|
||||||
|
try (ExecutorService executor = Executors.newSingleThreadExecutor()) {
|
||||||
|
Future<IrisObject> result;
|
||||||
|
source.writeLock.lock();
|
||||||
|
try {
|
||||||
|
result = executor.submit(() -> {
|
||||||
|
started.countDown();
|
||||||
|
return source.copy();
|
||||||
|
});
|
||||||
|
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||||
|
assertThrows(TimeoutException.class, () -> result.get(150, TimeUnit.MILLISECONDS));
|
||||||
|
source.setW(3);
|
||||||
|
source.setCenter(new Vector3i(1, 0, 0));
|
||||||
|
source.setUnsigned(2, 0, 0, block);
|
||||||
|
} finally {
|
||||||
|
source.writeLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisObject copy = result.get(5, TimeUnit.SECONDS);
|
||||||
|
assertEquals(3, copy.getW());
|
||||||
|
assertEquals(new Vector3i(1, 0, 0), copy.getCenter());
|
||||||
|
assertSame(block, copy.getBlocks().get(new IrisBlockVector(1, 0, 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void copyOwnsItsVolumeCoordinatesAndTiles() {
|
||||||
|
IrisObject source = new IrisObject(1, 1, 1);
|
||||||
|
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||||
|
TileData tile = new TileData("minecraft:chest", new KMap<>());
|
||||||
|
tile.getProperties().put("name", "source");
|
||||||
|
source.setUnsigned(0, 0, 0, block);
|
||||||
|
source.setUnsignedTile(0, 0, 0, tile);
|
||||||
|
|
||||||
|
IrisObject copy = source.copy();
|
||||||
|
IrisBlockVector position = new IrisBlockVector(0, 0, 0);
|
||||||
|
TileData copiedTile = copy.getStates().get(position);
|
||||||
|
assertNotSame(source.getCenter(), copy.getCenter());
|
||||||
|
assertNotSame(tile, copiedTile);
|
||||||
|
assertNotSame(tile.getProperties(), copiedTile.getProperties());
|
||||||
|
copiedTile.getProperties().put("name", "copy");
|
||||||
|
copy.getBlocks().remove(position);
|
||||||
|
|
||||||
|
assertEquals("source", tile.getProperties().get("name"));
|
||||||
|
assertSame(block, source.getBlocks().get(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import art.arcane.iris.spi.IrisPlatform;
|
|||||||
import art.arcane.iris.spi.IrisPlatforms;
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import art.arcane.iris.spi.PlatformRegistries;
|
import art.arcane.iris.spi.PlatformRegistries;
|
||||||
|
import art.arcane.iris.util.common.math.Vector3i;
|
||||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
@@ -101,6 +102,35 @@ public class NativeStructureObjectVetoTest {
|
|||||||
assertTrue(placer.written().isEmpty());
|
assertTrue(placer.written().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void obliqueRotationRejectsNativePiecesBeyondTheUnrotatedRadius() {
|
||||||
|
IrisObject object = new IrisObject(21, 1, 21);
|
||||||
|
object.setUnsigned(20, 0, 20, log);
|
||||||
|
IrisObjectPlacement placement = placement();
|
||||||
|
placement.setBottom(false);
|
||||||
|
placement.setRotation(IrisObjectRotation.of(0, 45, 0));
|
||||||
|
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||||
|
volumes(volume(14, SURFACE_Y, 0, 14, SURFACE_Y, 0));
|
||||||
|
|
||||||
|
assertEquals(-1, object.place(0, SURFACE_Y, 0, placer, placement, new RNG(1234L), data));
|
||||||
|
assertTrue(placer.written().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void offCenterOriginRejectsNativePiecesAtTheFarEdge() {
|
||||||
|
IrisObject object = new IrisObject(9, 1, 1);
|
||||||
|
object.setCenter(new Vector3i(0, 0, 0));
|
||||||
|
object.setUnsigned(8, 0, 0, log);
|
||||||
|
IrisObjectPlacement placement = placement();
|
||||||
|
placement.setBottom(false);
|
||||||
|
placement.setRotation(IrisObjectRotation.of(0, 0, 0));
|
||||||
|
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||||
|
volumes(volume(8, SURFACE_Y, 0, 8, SURFACE_Y, 0));
|
||||||
|
|
||||||
|
assertEquals(-1, object.place(0, SURFACE_Y, 0, placer, placement, new RNG(1234L), data));
|
||||||
|
assertTrue(placer.written().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void pieceOverlappingOnlyTheEnvelopeStillPlaces() {
|
public void pieceOverlappingOnlyTheEnvelopeStillPlaces() {
|
||||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||||
|
|||||||
Reference in new issue
Block a user