Whoops Core

This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-08 16:15:19 -04:00
1 parent 98e51cba8d
commit 0782bbf0b4
31 files changed
+1178 -190

No files matched your search

@@ -418,7 +418,7 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
JSONObject parsed = new JSONObject(rawText);
JsonSchemaValidator.validateTopLevelKeys(parsed, rawText, j, resourceTypeName, objectClass);
if (objectClass == IrisDimension.class) {
PackValidator.requireValidStaticObjects(manager.getDataFolder(), name, parsed);
PackValidator.requireValidObjectSettings(manager.getDataFolder(), name, parsed);
}
T t = getManager().getGson()
.fromJson(preprocess(parsed).toString(0), objectClass);
@@ -19,6 +19,7 @@
package art.arcane.iris.core.pack;
import art.arcane.iris.engine.object.IrisDimensionType;
import art.arcane.iris.engine.object.IrisObjectScale;
import art.arcane.iris.engine.object.IrisWorldBoundary;
import art.arcane.volmlib.util.json.JSONArray;
import art.arcane.volmlib.util.json.JSONObject;
@@ -57,6 +58,7 @@ final class PackDimensionValidator {
validateDimensionHeights(packFolder, dimensionKey, dimJson, blockingErrors);
validateWorldBoundary(dimensionKey, dimJson, blockingErrors);
validateStaticObjects(packFolder, dimensionKey, dimJson, blockingErrors);
validateObjectScaleFactor(dimensionKey, dimJson, blockingErrors);
validateDimensionStack(packFolder, dimensionKey, dimJson, dimensionKeys, blockingErrors);
JSONArray regionsArray = dimJson.optJSONArray("regions");
@@ -102,6 +104,11 @@ final class PackDimensionValidator {
}
}
static void validateObjectScaleFactor(String dimensionKey, JSONObject dimension, List<String> errors) {
validateFiniteNumber(dimension, "allObjectScaleFactor", IrisObjectScale.MINIMUM_FACTOR,
IrisObjectScale.MAXIMUM_FACTOR, "Dimension '" + dimensionKey + "'", errors);
}
static void validateDimensionStack(File packFolder, String dimensionKey, JSONObject dimension, Set<String> dimensionKeys,
List<String> blockingErrors) {
if (!dimension.has("dimensionStack")) {
@@ -86,9 +86,10 @@ public final class PackValidator {
return validate(packFolder, false);
}
public static void requireValidStaticObjects(File packFolder, String dimensionKey, JSONObject dimension) {
public static void requireValidObjectSettings(File packFolder, String dimensionKey, JSONObject dimension) {
List<String> errors = new ArrayList<>();
PackDimensionValidator.validateStaticObjects(packFolder, dimensionKey, dimension, errors);
PackDimensionValidator.validateObjectScaleFactor(dimensionKey, dimension, errors);
if (!errors.isEmpty()) {
throw new IllegalArgumentException(String.join("; ", errors));
}
@@ -150,7 +150,8 @@ public class TreeSVC implements IrisService {
}
saplingPlane.forEach(block -> block.setType(Material.AIR));
IrisObject object = worldAccess.getData().getObjectLoader().load(pool.getRandom(RNG.r));
IrisObject object = placement.scaleObject(RNG.r,
worldAccess.getData().getObjectLoader().load(pool.getRandom(RNG.r)), dimension);
if (object == null) {
return;
@@ -477,8 +477,9 @@ public class IrisStructureComponent extends IrisMantleComponent {
if (object == null) {
return;
}
int horizontalSpan = Math.max(1, Math.max(object.getW(), object.getD()));
int verticalSpan = Math.max(1, object.getH());
double scale = Math.max(1D, getDimension().getAllObjectScaleFactor());
int horizontalSpan = scaledMarkerSpan(Math.max(object.getW(), object.getD()), scale);
int verticalSpan = scaledMarkerSpan(object.getH(), scale);
ObjectMarkerBounds search = intersections.expand(horizontalSpan - 1, verticalSpan - 1, worldHeight);
KList<ObjectBlockPosition> positions = new KList<>();
int minChunkX = Math.floorDiv(search.minX, 16);
@@ -516,6 +517,11 @@ public class IrisStructureComponent extends IrisMantleComponent {
return decoded != null && !decoded.structureAware();
}
static int scaledMarkerSpan(int dimension, double scale) {
return scale <= 1D ? Math.max(1, dimension)
: (int) Math.ceil(Math.max(1, dimension) * scale);
}
static ObjectMarkerBounds markerSearchBounds(int minX, int minY, int minZ,
int maxX, int maxY, int maxZ,
int horizontalSpan, int verticalSpan,
@@ -188,7 +188,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
if (raw == null) {
continue;
}
IrisObject obj0 = placement.getScale().get(rng, raw);
IrisObject obj0 = placement.scaleObject(rng, raw, getDimension());
if (obj0 == null) {
continue;
}
@@ -248,7 +248,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
if (raw == null) {
continue;
}
IrisObject obj0 = placement.getScale().get(rng, raw);
IrisObject obj0 = placement.scaleObject(rng, raw, getDimension());
if (obj0 == null) {
continue;
}
@@ -340,7 +340,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
if (raw == null) {
continue;
}
IrisObject obj0 = placement.getScale().get(rng, raw);
IrisObject obj0 = placement.scaleObject(rng, raw, getDimension());
if (obj0 == null) {
continue;
}
@@ -628,7 +628,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
size = IrisObject.sampleSize(file);
sizeCache.put(objectKey, size);
}
int reach = MantleObjectComponent.calculatePlacementReach(size, placement);
int reach = MantleObjectComponent.calculatePlacementReach(size, placement, placement.getMaximumScale(getDimension()));
if (reach > 128 && warnedLargeObjects.add(objectKey)) {
IrisLogging.warn("Floating object " + objectKey + " has a large placement reach (" + reach + " blocks) and may increase memory usage!");
}
@@ -622,7 +622,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
IObjectPlacer placer = golden ? new GoldenDebugObjectPlacer(basePlacer, scope + "/" + p.getName()) : basePlacer;
int density = Math.max(1, p.getDensity());
for (int i = 0; i < density; i++) {
IrisObject variant = p.getVariantObject(getData(), rng);
IrisObject variant = placement.scaleObject(rng, p.getVariantObject(getData(), rng), getDimension());
if (variant == null) {
if (golden) {
IrisLogging.debug("Goldendebug procedural pick: chunk=" + x + "," + z
@@ -887,7 +887,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
for (int i = 0; i < density; i++) {
attempts++;
IrisObject v = objectPlacement.getScale().get(rng, objectPlacement.getObject(complex, rng));
IrisObject v = objectPlacement.scaleObject(rng, objectPlacement.getObject(complex, rng), getDimension());
if (v == null) {
nullObjects++;
if (traceRegen) {
@@ -1030,7 +1030,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
for (int i = 0; i < density; i++) {
attempts++;
IrisObject object = objectPlacement.getScale().get(rng, objectPlacement.getObject(complex, rng));
IrisObject object = objectPlacement.scaleObject(rng, objectPlacement.getObject(complex, rng), getDimension());
if (object == null) {
nullObjects++;
if (traceRegen) {
@@ -1261,7 +1261,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
int density = objectPlacement.getDensity(rng, minX, minZ, getData());
for (int i = 0; i < density; i++) {
IrisObject v = objectPlacement.getScale().get(rng, objectPlacement.getObject(complex, rng));
IrisObject v = objectPlacement.scaleObject(rng, objectPlacement.getObject(upperCtx, rng), upperCtx.getDimension());
if (v == null) {
continue;
}
@@ -1647,7 +1647,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
private Set<String> guessPlacedKeys(RNG rng, int x, int z, IrisObjectPlacement objectPlacement) {
Set<String> f = new KSet<>();
for (int i = 0; i < objectPlacement.getDensity(rng, x, z, getData()); i++) {
IrisObject v = objectPlacement.getScale().get(rng, objectPlacement.getObject(getComplex(), rng));
IrisObject v = objectPlacement.scaleObject(rng, objectPlacement.getObject(getComplex(), rng), getDimension());
if (v == null) {
continue;
}
@@ -1708,11 +1708,12 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
radius = Math.max(radius, computePlacementRadius(
region.getObjects(),
dimension,
dataProvider,
sizeCache,
warnedLargeObjects
));
radius = Math.max(radius, computeProceduralRadius(region.getProceduralObjects(), dataProvider));
radius = Math.max(radius, computeProceduralRadius(region.getProceduralObjects(), dimension, dataProvider));
}
for (IrisBiome biome : dimension.getReachableBiomes(dataProvider)) {
if (biome == null) {
@@ -1720,17 +1721,19 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
radius = Math.max(radius, computePlacementRadius(
biome.getObjects(),
dimension,
dataProvider,
sizeCache,
warnedLargeObjects
));
radius = Math.max(radius, computeProceduralRadius(biome.getProceduralObjects(), dataProvider));
radius = Math.max(radius, computeProceduralRadius(biome.getProceduralObjects(), dimension, dataProvider));
}
return radius;
}
private int computePlacementRadius(
KList<IrisObjectPlacement> placements,
IrisDimension dimension,
DataProvider dataProvider,
KMap<String, IrisBlockVector> sizeCache,
KSet<String> warnedLargeObjects
@@ -1746,7 +1749,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
if (size == null) {
continue;
}
int reach = calculatePlacementReach(size, placement);
int reach = calculatePlacementReach(size, placement, placement.getMaximumScale(dimension));
if (reach > 128 && warnedLargeObjects.add(objectKey)) {
IrisLogging.warn("Object " + objectKey + " has a large placement reach (" + reach + " blocks) and may increase memory usage!");
}
@@ -1759,7 +1762,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
return radius;
}
private int computeProceduralRadius(IrisProceduralObjects procedural, DataProvider dataProvider) {
private int computeProceduralRadius(IrisProceduralObjects procedural, IrisDimension dimension, DataProvider dataProvider) {
if (procedural == null || procedural.isEmpty()) {
return 0;
}
@@ -1778,13 +1781,13 @@ public class MantleObjectComponent extends IrisMantleComponent {
continue;
}
IrisBlockVector size = new IrisBlockVector(variant.getW(), variant.getH(), variant.getD());
radius = Math.max(radius, calculatePlacementReach(size, objectPlacement));
radius = Math.max(radius, calculatePlacementReach(size, objectPlacement, objectPlacement.getMaximumScale(dimension)));
}
}
return radius;
}
static int calculatePlacementReach(IrisBlockVector size, IrisObjectPlacement placement) {
static int calculatePlacementReach(IrisBlockVector size, IrisObjectPlacement placement, double scale) {
if (size == null) {
return 0;
}
@@ -1792,7 +1795,6 @@ public class MantleObjectComponent extends IrisMantleComponent {
return Math.max(Math.abs(size.getBlockX()), Math.abs(size.getBlockZ()));
}
double scale = placement.getScale() != null ? Math.max(1D, placement.getScale().getMaxScale()) : 1D;
int width = scaledDimension(size.getBlockX(), scale);
int height = scaledDimension(size.getBlockY(), scale);
int depth = scaledDimension(size.getBlockZ(), scale);
@@ -1823,7 +1825,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
int reach = calculatePlacementReach(
new IrisBlockVector(object.getW(), object.getH(), object.getD()),
placement);
placement, 1D);
return complex.allowsNewGenerationFootprint(
saturatedOffset(anchorX, -reach),
saturatedOffset(anchorZ, -reach),
@@ -1838,10 +1840,10 @@ public class MantleObjectComponent extends IrisMantleComponent {
private static int scaledDimension(int dimension, double scale) {
int absoluteDimension = Math.abs(dimension);
if (scale <= 1D) {
if (scale == 1D) {
return absoluteDimension;
}
return (int) Math.ceil((absoluteDimension * scale) + (scale * 2D));
return (int) Math.ceil(absoluteDimension * scale);
}
private static int calculateTranslationReach(IrisObjectTranslate translate, IrisObjectRotation rotation, boolean rotateVertically) {
@@ -186,6 +186,10 @@ public class IrisDimension extends IrisRegistrant {
@MaxNumber(16)
@Desc("Minimum surface-support buffer, in blocks, applied to every surface object placement in this dimension. A placement may ask for more but never less.")
private int objectSurfaceSupportBuffer = 2;
@MinNumber(IrisObjectScale.MINIMUM_FACTOR)
@MaxNumber(IrisObjectScale.MAXIMUM_FACTOR)
@Desc("Default size factor for objects in this dimension. 0.5 halves their size and 2 doubles it. Any explicit object or placement scale, including 1, overrides this factor. Jigsaw structures are excluded.")
private double allObjectScaleFactor = 1D;
@Desc("The world environment")
private IrisEnvironment environment = IrisEnvironment.NORMAL;
@RegistryListResource(IrisRegion.class)
@@ -319,6 +323,16 @@ public class IrisDimension extends IrisRegistrant {
return staticObjectLayer.aquire(() -> IrisStaticObjectLayer.compile(this, data));
}
public double getAllObjectScaleFactor() {
return IrisObjectScale.requireValidFactor(allObjectScaleFactor, "allObjectScaleFactor");
}
public IrisDimension setAllObjectScaleFactor(double factor) {
allObjectScaleFactor = IrisObjectScale.requireValidFactor(factor, "allObjectScaleFactor");
staticObjectLayer.reset();
return this;
}
/**
* Runs the version-content gate over every static object so the pack report lists the entries this server cannot
* place. The static layer makes the same decision when it compiles; validation calls this on its detached loader
@@ -162,8 +162,8 @@ public class IrisObjectPlacement {
private KList<IrisObjectReplace> edit = new KList<>();
@Desc("Translate this object's placement")
private IrisObjectTranslate translate = new IrisObjectTranslate();
@Desc("Scale Objects")
private IrisObjectScale scale = new IrisObjectScale();
@Desc("Explicit object scale. When omitted, the dimension's allObjectScaleFactor applies. Any authored scale, including size 1, overrides that factor.")
private IrisObjectScale scale;
@ArrayType(min = 1, type = IrisObjectLoot.class)
@Desc("The loot tables to apply to these objects")
private KList<IrisObjectLoot> loot = new KList<>();
@@ -363,6 +363,18 @@ public class IrisObjectPlacement {
return data.getObjectLoader().load(pool.get(random.nextInt(pool.size())));
}
public IrisObject scaleObject(RNG random, IrisObject object, IrisDimension dimension) {
return scale == null
? IrisObjectScale.getFixed(object, dimension == null ? 1D : dimension.getAllObjectScaleFactor())
: scale.get(random, object);
}
public double getMaximumScale(IrisDimension dimension) {
return scale == null
? dimension == null ? 1D : dimension.getAllObjectScaleFactor()
: scale.getMaxScale();
}
public boolean matches(IrisTreeSize size, TreeType type) {
for (IrisTree i : getTrees()) {
if (i.matches(size, type)) {
@@ -125,6 +125,7 @@ final class IrisObjectPlacementRunner {
boolean preventingDecay = placer.isPreventingDecay();
boolean waterlogCandidate = config.isWaterloggable() || config.isUnderwater();
boolean rawStructurePiece = config.getMode() == ObjectPlaceMode.STRUCTURE_PIECE;
boolean paint = yv < 0 && config.getMode() == ObjectPlaceMode.PAINT;
boolean organicFloor = config.getMode() == ObjectPlaceMode.ORGANIC_STILT;
boolean ceilingHang = config.getMode() == ObjectPlaceMode.CEILING_HANG;
boolean organic = organicFloor || ceilingHang;
@@ -257,7 +258,10 @@ final class IrisObjectPlacementRunner {
}
}
} else if (config.getMode().equals(ObjectPlaceMode.PAINT)) {
y = placer.getHighest(x, z, self.getLoader(), config.isUnderwater()) + rty;
int anchorX = x + (translating ? (int) Math.round(translateOffset.getX()) : 0);
int anchorZ = z + (translating ? (int) Math.round(translateOffset.getZ()) : 0);
y = (c != null ? c.getSurface()
: placer.getHighest(anchorX, anchorZ, self.getLoader(), config.isUnderwater())) + rty;
if (!config.isForcePlace()) {
if (shouldBailForCarvingAnchor(placer, config, x, y, z)) {
bail = true;
@@ -302,12 +306,30 @@ final class IrisObjectPlacementRunner {
return -1;
}
IrisPaintSurfaceProjection paintProjection = null;
KList<IrisBlockVector> paintSupport = null;
if (paint) {
int buffer = config.isRequireSurfaceSupport() ? Math.max(0, config.getSurfaceSupportBuffer()) : 0;
int anchorX = x + (translating ? (int) Math.round(translateOffset.getX()) : 0);
int anchorZ = z + (translating ? (int) Math.round(translateOffset.getZ()) : 0);
paintProjection = IrisPaintSurfaceProjection.create(placer, new IrisPaintSurfaceProjection.Request(
self.getLoader(), x + placementBounds.minX() - buffer, x + placementBounds.maxX() + buffer,
z + placementBounds.minZ() - buffer, z + placementBounds.maxZ() + buffer,
anchorX, y - rty, anchorZ, config.isUnderwater(), config.getHeightmap() != null));
if (paintProjection == null) {
return -1;
}
paintSupport = transformedPaintSupport(spin, translateOffset, surfaceWarp, warpHalf, x, y + yrand, z);
}
if (yv < 0
&& config.getCarvingSupport().supportsSurface()
&& config.getMode() != ObjectPlaceMode.FLOATING
&& !rawStructurePiece
&& IrisSurfaceSupport.intersectsHydrology(oplacer, x, z, config.getTranslate(),
config.getRotation(), spinx, spiny, spinz, self.getSurfaceSupportOffsets())) {
&& (paint ? IrisSurfaceSupport.intersectsHydrology(oplacer, x, z, null, null,
0, 0, 0, paintSupport)
: IrisSurfaceSupport.intersectsHydrology(oplacer, x, z, config.getTranslate(),
config.getRotation(), spinx, spiny, spinz, self.getSurfaceSupportOffsets()))) {
return -1;
}
@@ -325,9 +347,12 @@ final class IrisObjectPlacementRunner {
&& !config.isUnderwater()
&& !config.isOnwater()
&& config.isRequireSurfaceSupport()
&& IrisSurfaceSupport.isUnsupported(oplacer, self.getLoader(), x, z, config.getTranslate(),
&& (paint ? IrisSurfaceSupport.isUnsupported(oplacer, self.getLoader(), x, z, null, null,
0, 0, 0, paintSupport, config.getSurfaceSupportBuffer(), config.getSurfaceSupportDepth(),
paintProjection)
: IrisSurfaceSupport.isUnsupported(oplacer, self.getLoader(), x, z, config.getTranslate(),
config.getRotation(), spinx, spiny, spinz, self.getSurfaceSupportOffsets(),
config.getSurfaceSupportBuffer(), config.getSurfaceSupportDepth())) {
config.getSurfaceSupportBuffer(), config.getSurfaceSupportDepth()))) {
return -1;
}
@@ -351,22 +376,27 @@ final class IrisObjectPlacementRunner {
}
if (!rawStructurePiece && nativeStructureVetoes(placer, config, spin, placementBounds, translating, translateOffset, ceilingHang,
yv < 0 && config.getMode() == ObjectPlaceMode.PAINT, warpMargin, x, y + yrand, z)) {
paintProjection, surfaceWarp, warpHalf, yrand, warpMargin, x, y + yrand, z)) {
return -1;
}
boolean paint = yv < 0 && config.getMode() == ObjectPlaceMode.PAINT;
WorldBounds worldBounds = null;
if (config.isBore() || (!config.isForcePlace() && !rawStructurePiece
&& (!config.getAllowedCollisions().isEmpty() || !config.getForbiddenCollisions().isEmpty()))) {
worldBounds = resolveWorldBounds(placer, config, placementBounds, paint, x, y + yrand, z);
worldBounds = resolveWorldBounds(placementBounds, paintProjection, yrand, x, y + yrand, z);
}
if (!config.isForcePlace() && !rawStructurePiece && (!config.getAllowedCollisions().isEmpty() || !config.getForbiddenCollisions().isEmpty())) {
Engine engine = requireDataEngine(rdata, "collision settings");
for (int i = worldBounds.minX(); i <= worldBounds.maxX(); i++) {
for (int j = worldBounds.minY(); j <= worldBounds.maxY(); j++) {
for (int k = worldBounds.minZ(); k <= worldBounds.maxZ(); k++) {
for (int k = worldBounds.minZ(); k <= worldBounds.maxZ(); k++) {
int surface = paint ? paintProjection.surfaceY(i, k) : 0;
if (surface == IrisPaintSurfaceProjection.MISSING) {
continue;
}
int minimumY = paint ? surface + placementBounds.minY() + Math.floorDiv(self.h, 2) + yrand : worldBounds.minY();
int maximumY = paint ? surface + placementBounds.maxY() + Math.floorDiv(self.h, 2) + yrand : worldBounds.maxY();
for (int j = minimumY; j <= maximumY; j++) {
String placementMarker = placer.getData(i, j, k, String.class);
PlacedObject p = engine.resolveObjectPlacementMarker(i, k, placementMarker);
if (p == null) continue;
@@ -387,8 +417,14 @@ final class IrisObjectPlacementRunner {
if (config.isBore()) {
for (int i = worldBounds.minX(); i <= worldBounds.maxX(); i++) {
for (int j = worldBounds.minY() - config.getBoreExtendMinY(); j <= worldBounds.maxY() + config.getBoreExtendMaxY(); j++) {
for (int k = worldBounds.minZ(); k <= worldBounds.maxZ(); k++) {
for (int k = worldBounds.minZ(); k <= worldBounds.maxZ(); k++) {
int surface = paint ? paintProjection.surfaceY(i, k) : 0;
if (surface == IrisPaintSurfaceProjection.MISSING) {
continue;
}
int minimumY = paint ? surface + placementBounds.minY() + Math.floorDiv(self.h, 2) + yrand : worldBounds.minY();
int maximumY = paint ? surface + placementBounds.maxY() + Math.floorDiv(self.h, 2) + yrand : worldBounds.maxY();
for (int j = minimumY - config.getBoreExtendMinY(); j <= maximumY + config.getBoreExtendMaxY(); j++) {
placer.set(i, j, k, IrisObject.States.air());
}
}
@@ -546,8 +582,14 @@ final class IrisObjectPlacementRunner {
zz += surfaceWarp.fitDouble(-warpHalf, warpHalf, i.getZ() + z, i.getY() + y, i.getX() + x);
}
if (yv < 0 && (config.getMode().equals(ObjectPlaceMode.PAINT)) && !B.isVineBlock(data)) {
yy = (int) Math.round(i.getY()) + Math.floorDiv(self.h, 2) + placer.getHighest(xx, zz, self.getLoader(), config.isUnderwater());
if (paint) {
int surface = paintProjection.surfaceY(xx, zz);
if (surface == IrisPaintSurfaceProjection.MISSING) {
continue;
}
if (!B.isVineBlock(data)) {
yy = (int) Math.round(i.getY()) + Math.floorDiv(self.h, 2) + surface + yrand;
}
}
if (config.isMeld() && !rawStructurePiece && !placer.isSolid(xx, yy, zz)) {
@@ -864,6 +906,26 @@ final class IrisObjectPlacementRunner {
return !wouldReplace && (rawStructurePiece || !air);
}
private KList<IrisBlockVector> transformedPaintSupport(SpinKernel spin, IrisBlockVector translateOffset,
CNG warp, double half, int x, int y, int z) {
KList<IrisBlockVector> offsets = new KList<>();
for (IrisBlockVector source : self.getSurfaceSupportOffsets()) {
IrisBlockVector offset = source.clone();
spin.rotate(offset);
if (translateOffset != null) {
offset.add(translateOffset);
}
int worldX = x + (int) Math.round(offset.getX());
int worldZ = z + (int) Math.round(offset.getZ());
if (warp != null) {
worldX += warp.fitDouble(-half, half, offset.getX() + x, offset.getY() + y, offset.getZ() + z);
worldZ += warp.fitDouble(-half, half, offset.getZ() + z, offset.getY() + y, offset.getX() + x);
}
offsets.add(new IrisBlockVector(worldX - x, 0, worldZ - z));
}
return offsets;
}
private TransformedBounds transformedBounds(SpinKernel spin, boolean translating, IrisBlockVector translateOffset,
boolean ceilingHang, int margin) {
int sourceMinX = -self.getCenter().getBlockX();
@@ -909,13 +971,13 @@ final class IrisObjectPlacementRunner {
return new TransformedBounds(minX - margin, maxX + margin, minY, maxY, minZ - margin, maxZ + margin);
}
private WorldBounds resolveWorldBounds(IObjectPlacer placer, IrisObjectPlacement config, TransformedBounds bounds,
boolean paint, int x, int y, int z) {
private WorldBounds resolveWorldBounds(TransformedBounds bounds, IrisPaintSurfaceProjection projection,
int randomY, int x, int y, int z) {
int minX = x + bounds.minX();
int maxX = x + bounds.maxX();
int minZ = z + bounds.minZ();
int maxZ = z + bounds.maxZ();
if (!paint) {
if (projection == null) {
return new WorldBounds(minX, maxX, y + bounds.minY(), y + bounds.maxY(), minZ, maxZ);
}
@@ -923,12 +985,15 @@ final class IrisObjectPlacementRunner {
int maximumSurface = Integer.MIN_VALUE;
for (int worldX = minX; worldX <= maxX; worldX++) {
for (int worldZ = minZ; worldZ <= maxZ; worldZ++) {
int surface = placer.getHighest(worldX, worldZ, self.getLoader(), config.isUnderwater());
int surface = projection.surfaceY(worldX, worldZ);
if (surface == IrisPaintSurfaceProjection.MISSING) {
continue;
}
minimumSurface = Math.min(minimumSurface, surface);
maximumSurface = Math.max(maximumSurface, surface);
}
}
int paintOffset = Math.floorDiv(self.h, 2);
int paintOffset = Math.floorDiv(self.h, 2) + randomY;
return new WorldBounds(minX, maxX, minimumSurface + bounds.minY() + paintOffset,
maximumSurface + bounds.maxY() + paintOffset, minZ, maxZ);
}
@@ -960,7 +1025,8 @@ final class IrisObjectPlacementRunner {
*/
private boolean nativeStructureVetoes(IObjectPlacer placer, IrisObjectPlacement config, SpinKernel spin, TransformedBounds bounds,
boolean translating, IrisBlockVector translateOffset, boolean ceilingHang,
boolean paint, int warpMargin, int x, int y, int z) {
IrisPaintSurfaceProjection projection, CNG surfaceWarp, double warpHalf,
int randomY, int warpMargin, int x, int y, int z) {
Engine engine = placer.getEngine();
if (engine == null) {
return false;
@@ -976,8 +1042,8 @@ final class IrisObjectPlacementRunner {
}
int worldY = y + engine.getMinHeight();
int envelopeMinY = paint ? Integer.MIN_VALUE : worldY + bounds.minY() - warpMargin;
int envelopeMaxY = paint ? Integer.MAX_VALUE : worldY + bounds.maxY() + warpMargin;
int envelopeMinY = projection != null ? Integer.MIN_VALUE : worldY + bounds.minY() - warpMargin;
int envelopeMaxY = projection != null ? Integer.MAX_VALUE : worldY + bounds.maxY() + warpMargin;
boolean envelopeMeetsPiece = false;
for (NativeStructureVolume volume : volumes) {
if (volume.intersects(minX, envelopeMinY, minZ, maxX, envelopeMaxY, maxZ)) {
@@ -1010,14 +1076,24 @@ final class IrisObjectPlacementRunner {
int xx = x + (int) Math.round(i.getX());
int zz = z + (int) Math.round(i.getZ());
int yy = paint
? (int) Math.round(i.getY()) + Math.floorDiv(self.h, 2)
+ placer.getHighest(xx, zz, self.getLoader(), config.isUnderwater())
: y + (int) Math.round(i.getY());
int yy = y + (int) Math.round(i.getY());
if (projection != null) {
if (surfaceWarp != null) {
xx += surfaceWarp.fitDouble(-warpHalf, warpHalf, i.getX() + x, i.getY() + y, i.getZ() + z);
zz += surfaceWarp.fitDouble(-warpHalf, warpHalf, i.getZ() + z, i.getY() + y, i.getX() + x);
}
int surface = projection.surfaceY(xx, zz);
if (surface == IrisPaintSurfaceProjection.MISSING) {
continue;
}
if (!B.isVineBlock(state)) {
yy = (int) Math.round(i.getY()) + Math.floorDiv(self.h, 2) + surface + randomY;
}
}
int worldBlockY = yy + engine.getMinHeight();
for (NativeStructureVolume volume : volumes) {
if (volume.containsWithin(xx, worldBlockY, zz, warpMargin)) {
if (volume.containsWithin(xx, worldBlockY, zz, projection == null ? warpMargin : 0)) {
return true;
}
}
@@ -43,6 +43,8 @@ import java.util.Map;
@Desc("Scale objects")
@Data
public class IrisObjectScale {
public static final double MINIMUM_FACTOR = 0.01D;
public static final double MAXIMUM_FACTOR = 50D;
private static final long CACHE_MIN_ESTIMATED_BYTES = 16L * 1024L * 1024L;
private static final long CACHE_MAX_ESTIMATED_BYTES = 64L * 1024L * 1024L;
private static final long CACHE_ENTRY_ESTIMATED_BYTES = 512L;
@@ -88,15 +90,32 @@ public class IrisObjectScale {
return minimumScale != 1 || maximumScale != 1;
}
public IrisObjectScale setSize(double size) {
this.size = requireValidFactor(size, "size");
return this;
}
public IrisObjectScale setMinimumScale(double minimumScale) {
this.minimumScale = requireValidFactor(minimumScale, "minimumScale");
return this;
}
public IrisObjectScale setMaximumScale(double maximumScale) {
this.maximumScale = requireValidFactor(maximumScale, "maximumScale");
return this;
}
public int getMaxSizeFor(int indim) {
return (int) Math.ceil(getMaxScale() * indim);
}
public double getMaxScale() {
if (size != 1) {
return size;
ScaleRequest request = snapshotRequest();
if (!request.shouldScale()) {
return 1D;
}
return Math.max(minimumScale, maximumScale);
return request.size() != 1D ? request.size()
: Math.max(request.minimumScale(), request.maximumScale());
}
public IrisObject get(RNG rng, IrisObject origin) {
@@ -109,7 +128,27 @@ public class IrisObjectScale {
return origin;
}
int variantIndex = request.selectVariant(rng);
return get(origin, request, request.selectVariant(rng));
}
public static IrisObject getFixed(IrisObject origin, double factor) {
requireValidFactor(factor, "Object scale");
if (origin == null || factor == 1D) {
return origin;
}
return get(origin, new ScaleRequest(factor, 1D, 1D, 7,
IrisObjectPlacementScaleInterpolator.NONE), 0);
}
public static double requireValidFactor(double factor, String name) {
if (!Double.isFinite(factor) || factor < MINIMUM_FACTOR || factor > MAXIMUM_FACTOR) {
throw new IllegalArgumentException(name + " must be finite and between "
+ MINIMUM_FACTOR + " and " + MAXIMUM_FACTOR + ".");
}
return factor;
}
private static IrisObject get(IrisObject origin, ScaleRequest request, int variantIndex) {
CacheKey key = CACHE.key(origin, request, variantIndex);
CacheLookup lookup = CACHE.lookup(key);
if (lookup.variant() != null) {
@@ -145,6 +184,9 @@ public class IrisObjectScale {
}
private ScaleRequest snapshotRequest() {
requireValidFactor(size, "size");
requireValidFactor(minimumScale, "minimumScale");
requireValidFactor(maximumScale, "maximumScale");
IrisObjectPlacementScaleInterpolator configuredInterpolation = interpolation == null
? IrisObjectPlacementScaleInterpolator.NONE
: interpolation;
@@ -88,130 +88,99 @@ final class IrisObjectTransforms {
}
static IrisObject scaledAroundOrigin(IrisObject self, double scale, IrisObjectPlacementScaleInterpolator interpolation) {
if (!Double.isFinite(scale) || scale <= 0 || scale > 50) {
throw new IllegalArgumentException("Object scale must be finite, greater than zero, and at most 50");
}
return scaled(self, scale, interpolation, ScaleOrigin.SAVED);
}
private static IrisObject scaled(IrisObject self, double scale, IrisObjectPlacementScaleInterpolator interpolation,
ScaleOrigin origin) {
IrisObjectScale.requireValidFactor(scale, "Object scale");
if (interpolation == null) {
interpolation = IrisObjectPlacementScaleInterpolator.NONE;
}
boolean savedOrigin = origin == ScaleOrigin.SAVED;
IrisVector sm1 = new IrisVector(scale - 1, scale - 1, scale - 1);
scale = Math.max(0.001, Math.min(50, scale));
if (!savedOrigin && scale < 1) {
scale = scale - 0.0001;
}
VectorMap<PlatformBlockState> placeBlock = new VectorMap<>();
VectorMap<TileData> placeTile = new VectorMap<>();
VectorMap<IrisBlockVector> placeMax = savedOrigin && scale > 1 ? new VectorMap<>() : null;
IrisVector center;
IrisObject oo;
boolean hasTiles;
List<ScaledVoxel> voxels;
IrisObject output;
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();
output = savedOrigin ? createOriginScaledObject(self, scale)
: createCenteredScaledObject(self, scale);
output.setLoadKey(self.getLoadKey());
output.setLoader(self.getLoader());
output.setLoadFile(self.getLoadFile());
voxels = new ArrayList<>(self.blocks.size());
Vector3i sourceCenter = self.getCenter();
IrisBlockVector targetCenter = new IrisBlockVector(output.getCenter().getX(),
output.getCenter().getY(), output.getCenter().getZ());
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : self.blocks) {
PlatformBlockState bd = entry.getValue();
IrisBlockVector sourcePosition = entry.getKey();
IrisBlockVector position = savedOrigin
? scaledMinimum(sourcePosition, scale)
: sourcePosition.clone().add(IrisObject.HALF).subtract(center)
.multiply(scale).add(sm1).toBlockVector();
placeBlock.put(position, bd);
if (placeMax != null) {
placeMax.put(position, scaledMaximum(sourcePosition, scale));
}
if (hasTiles) {
TileData tile = self.states.get(entry.getKey());
if (tile == null) {
placeTile.remove(position);
} else {
placeTile.put(position, tile);
}
IrisBlockVector position = savedOrigin ? sourcePosition : new IrisBlockVector(
sourcePosition.getBlockX() + sourceCenter.getX(),
sourcePosition.getBlockY() + sourceCenter.getY(),
sourcePosition.getBlockZ() + sourceCenter.getZ());
IrisBlockVector minimum = scaledMinimum(position, scale);
IrisBlockVector maximum = scaledMaximum(position, scale);
if (!savedOrigin) {
minimum.subtract(targetCenter);
maximum.subtract(targetCenter);
}
TileData tile = self.states.get(sourcePosition);
voxels.add(new ScaledVoxel(sourcePosition.clone(), minimum, maximum, entry.getValue(),
tile == null ? null : tile.clone()));
}
} finally {
self.readLock.unlock();
}
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : placeBlock) {
IrisBlockVector v = entry.getKey();
TileData tile = hasTiles ? placeTile.get(v) : null;
if (scale > 1) {
IrisVector minimum = savedOrigin ? v : v.clone().add(center);
IrisVector maximum = savedOrigin ? placeMax.get(v) : v.clone().add(center).add(sm1);
int minX = Math.min(minimum.getBlockX(), maximum.getBlockX());
int maxX = Math.max(minimum.getBlockX(), maximum.getBlockX());
int minY = Math.min(minimum.getBlockY(), maximum.getBlockY());
int maxY = Math.max(minimum.getBlockY(), maximum.getBlockY());
int minZ = Math.min(minimum.getBlockZ(), maximum.getBlockZ());
int maxZ = Math.max(minimum.getBlockZ(), maximum.getBlockZ());
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());
}
}
}
}
}
} else {
IrisBlockVector position = savedOrigin
? v
: oo.getSigned(v.getBlockX(), v.getBlockY(), v.getBlockZ());
oo.blocks.put(position, entry.getValue());
if (hasTiles) {
if (tile == null) {
oo.states.remove(position);
} else {
oo.states.put(position, tile.clone());
}
}
}
if (scale != Math.rint(scale)) {
voxels.sort(Comparator.comparingInt((ScaledVoxel voxel) -> voxel.source().getBlockX())
.thenComparingInt(voxel -> voxel.source().getBlockY())
.thenComparingInt(voxel -> voxel.source().getBlockZ()));
}
for (ScaledVoxel voxel : voxels) {
writeScaledVoxel(output, voxel);
}
VectorMap<PlatformBlockState> scaledBlocks = oo.blocks;
VectorMap<PlatformBlockState> scaledBlocks = output.blocks;
if (scale > 1) {
switch (interpolation) {
case TRILINEAR -> trilinear(oo, (int) Math.round(scale));
case TRICUBIC -> tricubic(oo, (int) Math.round(scale));
case TRIHERMITE -> trihermite(oo, (int) Math.round(scale));
case TRILINEAR -> trilinear(output, (int) Math.round(scale));
case TRICUBIC -> tricubic(output, (int) Math.round(scale));
case TRIHERMITE -> trihermite(output, (int) Math.round(scale));
}
}
removeInapplicableTiles(output, scaledBlocks);
return output;
}
removeInapplicableTiles(oo, scaledBlocks);
return oo;
private static void writeScaledVoxel(IrisObject output, ScaledVoxel voxel) {
IrisBlockVector minimum = voxel.minimum();
IrisBlockVector maximum = voxel.maximum();
IrisBlockVector position = minimum.clone();
for (int x = minimum.getBlockX(); x <= maximum.getBlockX(); x++) {
position.setX(x);
for (int z = minimum.getBlockZ(); z <= maximum.getBlockZ(); z++) {
position.setZ(z);
for (int y = minimum.getBlockY(); y <= maximum.getBlockY(); y++) {
position.setY(y);
output.blocks.put(position, voxel.block());
if (voxel.tile() == null) {
output.states.remove(position);
} else {
output.states.put(position, voxel.tile().clone());
}
}
}
}
}
private static IrisObject createCenteredScaledObject(IrisObject source, double scale) {
IrisObject object = new IrisObject((int) Math.ceil(source.w * scale),
(int) Math.ceil(source.h * scale), (int) Math.ceil(source.d * scale));
Vector3i center = object.getCenter();
object.aabb.aquire(() -> new AxisAlignedBB(
new IrisPosition(-center.getX(), -center.getY(), -center.getZ()),
new IrisPosition(object.w - center.getX() - 1, object.h - center.getY() - 1,
object.d - center.getZ() - 1)));
return object;
}
private static IrisObject createOriginScaledObject(IrisObject source, double scale) {
@@ -499,6 +468,10 @@ final class IrisObjectTransforms {
private record NearestBlock(int x, int y, int z, int rank, PlatformBlockState state) {
}
private record ScaledVoxel(IrisBlockVector source, IrisBlockVector minimum, IrisBlockVector maximum,
PlatformBlockState block, TileData tile) {
}
private enum ScaleOrigin {
DEFAULT,
SAVED
@@ -0,0 +1,138 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import java.util.Arrays;
final class IrisPaintSurfaceProjection {
static final int MISSING = Integer.MIN_VALUE;
private static final int MAXIMUM_STEP = 4;
private static final int MAXIMUM_CELLS = 1 << 22;
private final Request request;
private final int depth;
private final int[] surfaces;
private IrisPaintSurfaceProjection(Request request, int[] surfaces) {
this.request = request;
depth = request.maxZ() - request.minZ() + 1;
this.surfaces = surfaces;
}
static IrisPaintSurfaceProjection create(IObjectPlacer placer, Request request) {
long width = (long) request.maxX() - request.minX() + 1;
long depth = (long) request.maxZ() - request.minZ() + 1;
long cells = width * depth;
if (width <= 0 || depth <= 0 || width > MAXIMUM_CELLS || depth > MAXIMUM_CELLS
|| cells > MAXIMUM_CELLS || request.anchorX() < request.minX()
|| request.anchorX() > request.maxX() || request.anchorZ() < request.minZ()
|| request.anchorZ() > request.maxZ()) {
return null;
}
int[] surfaces = new int[(int) cells];
Arrays.fill(surfaces, MISSING);
IrisPaintSurfaceProjection projection = new IrisPaintSurfaceProjection(request, surfaces);
return projection.resolve(placer) ? projection : null;
}
int surfaceY(int x, int z) {
if (x < request.minX() || x > request.maxX() || z < request.minZ() || z > request.maxZ()) {
return MISSING;
}
return surfaces[index(x, z)];
}
private boolean resolve(IObjectPlacer placer) {
int[] highest = new int[surfaces.length];
Arrays.fill(highest, MISSING);
int[] pending = new int[surfaces.length];
int anchor = index(request.anchorX(), request.anchorZ());
highest[anchor] = placer.getHighest(request.anchorX(), request.anchorZ(), request.data(), request.ignoreFluid());
if (!accepts(placer, request.anchorX(), request.anchorZ(), request.anchorY(), highest[anchor])) {
return false;
}
surfaces[anchor] = request.anchorY();
pending[0] = anchor;
int count = 1;
for (int cursor = 0; cursor < count; cursor++) {
int cell = pending[cursor];
int x = request.minX() + cell / depth;
int z = request.minZ() + cell % depth;
int reference = surfaces[cell];
count = visit(placer, (long) x - 1, z, reference, highest, pending, count);
count = visit(placer, (long) x + 1, z, reference, highest, pending, count);
count = visit(placer, x, (long) z - 1, reference, highest, pending, count);
count = visit(placer, x, (long) z + 1, reference, highest, pending, count);
}
return true;
}
private int visit(IObjectPlacer placer, long candidateX, long candidateZ, int reference,
int[] highest, int[] pending, int count) {
if (candidateX < request.minX() || candidateX > request.maxX()
|| candidateZ < request.minZ() || candidateZ > request.maxZ()) {
return count;
}
int x = (int) candidateX;
int z = (int) candidateZ;
int cell = index(x, z);
if (surfaces[cell] != MISSING) {
return count;
}
if (highest[cell] == MISSING) {
highest[cell] = placer.getHighest(x, z, request.data(), request.ignoreFluid());
}
for (int distance = 0; distance <= MAXIMUM_STEP; distance++) {
long lower = (long) reference - distance;
if (accepts(placer, x, z, lower, highest[cell])) {
surfaces[cell] = (int) lower;
pending[count++] = cell;
return count;
}
long upper = (long) reference + distance;
if (distance > 0 && accepts(placer, x, z, upper, highest[cell])) {
surfaces[cell] = (int) upper;
pending[count++] = cell;
return count;
}
}
return count;
}
private boolean accepts(IObjectPlacer placer, int x, int z, long y, int highest) {
if (y <= Integer.MIN_VALUE || y >= Integer.MAX_VALUE || y > highest
|| !matchesNeighbour((long) x - 1, z, y) || !matchesNeighbour((long) x + 1, z, y)
|| !matchesNeighbour(x, (long) z - 1, y) || !matchesNeighbour(x, (long) z + 1, y)) {
return false;
}
if (y == highest) {
return request.virtualHeightmap()
|| solid(placer, x, (int) y, z)
|| !request.ignoreFluid() && y == placer.getFluidHeight(x, z);
}
if (request.virtualHeightmap()) {
return false;
}
return solid(placer, x, (int) y, z) && !solid(placer, x, (int) y + 1, z);
}
private boolean matchesNeighbour(long x, long z, long y) {
if (x < request.minX() || x > request.maxX() || z < request.minZ() || z > request.maxZ()) {
return true;
}
int neighbour = surfaceY((int) x, (int) z);
return neighbour == MISSING || Math.abs(y - neighbour) <= MAXIMUM_STEP;
}
private boolean solid(IObjectPlacer placer, int x, int y, int z) {
return placer.isSurfaceSolid(x, y, z) && !placer.isCarved(x, y, z);
}
private int index(int x, int z) {
return (x - request.minX()) * depth + z - request.minZ();
}
record Request(IrisData data, int minX, int maxX, int minZ, int maxZ,
int anchorX, int anchorY, int anchorZ, boolean ignoreFluid, boolean virtualHeightmap) {
}
}
@@ -40,8 +40,8 @@ public class IrisStaticObject {
private IrisStaticObjectRotation rotation = new IrisStaticObjectRotation();
@MinNumber(MINIMUM_SCALE)
@MaxNumber(MAXIMUM_SCALE)
@Desc("Fixed size multiplier. 1 preserves the saved size, 0.5 halves it and 2 doubles it.")
private double scale = 1D;
@Desc("Explicit fixed size multiplier. When omitted, the dimension's allObjectScaleFactor applies. An explicit 1 preserves the saved size.")
private Double scale;
@Desc("Interpolation used when enlarging the object. NONE preserves blocky shapes.")
private IrisObjectPlacementScaleInterpolator scaleInterpolation = IrisObjectPlacementScaleInterpolator.NONE;
@ArrayType(type = IrisObjectReplace.class)
@@ -74,9 +74,8 @@ public class IrisStaticObject {
throw new IllegalArgumentException("rotation must be an object.");
}
rotation.validate();
if (!Double.isFinite(scale) || scale < MINIMUM_SCALE || scale > MAXIMUM_SCALE) {
throw new IllegalArgumentException("scale must be finite and between "
+ MINIMUM_SCALE + " and " + MAXIMUM_SCALE + ".");
if (scale != null) {
IrisObjectScale.requireValidFactor(scale, "scale");
}
if (scaleInterpolation == null) {
throw new IllegalArgumentException("scaleInterpolation must be a supported interpolation mode.");
@@ -141,12 +140,23 @@ public class IrisStaticObject {
.setMode(ObjectPlaceMode.STRUCTURE_PIECE)
.setForcePlace(true)
.setRotation(rotation.toRotation())
.setScale(new IrisObjectScale().setSize(scale).setInterpolation(scaleInterpolation))
.setScale(scale == null ? null
: new IrisObjectScale().setSize(scale).setInterpolation(scaleInterpolation))
.setEdit(new KList<>(edit))
.setBore(bore)
.setSmartBore(smartBore);
}
public IrisStaticObject setScale(Double scale) {
this.scale = scale == null ? null : IrisObjectScale.requireValidFactor(scale, "scale");
return this;
}
public double resolveScale(IrisDimension dimension) {
return scale == null ? dimension.getAllObjectScaleFactor()
: IrisObjectScale.requireValidFactor(scale, "scale");
}
private static void validateEdit(IrisObjectReplace replacement, int index) {
if (replacement.getFind() == null || replacement.getFind().isEmpty()
|| replacement.getFind().contains(null)) {
@@ -55,8 +55,9 @@ public final class IrisStaticObjectLayer {
if (object == null) {
throw new IllegalArgumentException("Object '" + entry.getObject() + "' could not be loaded");
}
if (entry.getScale() != 1D) {
object = object.scaledAroundOrigin(entry.getScale(), entry.getScaleInterpolation());
double scale = entry.resolveScale(dimension);
if (scale != 1D) {
object = object.scaledAroundOrigin(scale, entry.getScaleInterpolation());
} else if (entry.isSmartBore()) {
object = object.copy();
}
@@ -24,11 +24,20 @@ public final class IrisSurfaceSupport {
IrisObjectTranslate translate, IrisObjectRotation rotation,
int spinX, int spinY, int spinZ,
List<IrisBlockVector> supportOffsets, int buffer, int minSolidDepth) {
return isUnsupported(placer, data, x, z, translate, rotation, spinX, spinY, spinZ,
supportOffsets, buffer, minSolidDepth, null);
}
static boolean isUnsupported(IObjectPlacer placer, IrisData data, int x, int z,
IrisObjectTranslate translate, IrisObjectRotation rotation,
int spinX, int spinY, int spinZ,
List<IrisBlockVector> supportOffsets, int buffer, int minSolidDepth,
IrisPaintSurfaceProjection projection) {
Stencil stencil = STENCIL.get();
if (!stencil.build(supportOffsets, translate, rotation, spinX, spinY, spinZ, Math.max(0, buffer))) {
return true;
}
return stencil.anyColumnUnsupported(placer, data, x, z, Math.max(1, minSolidDepth));
return stencil.anyColumnUnsupported(placer, data, x, z, Math.max(1, minSolidDepth), projection);
}
public static boolean intersectsHydrology(IObjectPlacer placer, int x, int z,
@@ -165,13 +174,14 @@ public final class IrisSurfaceSupport {
}
/** Centre-outward so the common failure, a hole right under the anchor, costs one column. */
private boolean anyColumnUnsupported(IObjectPlacer placer, IrisData data, int x, int z, int minSolidDepth) {
private boolean anyColumnUnsupported(IObjectPlacer placer, IrisData data, int x, int z, int minSolidDepth,
IrisPaintSurfaceProjection projection) {
int maxRing = Math.max(
Math.max(centerX, width - 1 - centerX),
Math.max(centerZ, depth - 1 - centerZ));
for (int ring = 0; ring <= maxRing; ring++) {
if (ring == 0) {
if (isColumnUnsupported(placer, data, x, z, centerX, centerZ, minSolidDepth)) {
if (isColumnUnsupported(placer, data, x, z, centerX, centerZ, minSolidDepth, projection)) {
return true;
}
continue;
@@ -182,14 +192,14 @@ public final class IrisSurfaceSupport {
int lowZ = centerZ - ring;
int highZ = centerZ + ring;
for (int sx = lowX; sx <= highX; sx++) {
if (isColumnUnsupported(placer, data, x, z, sx, lowZ, minSolidDepth)
|| isColumnUnsupported(placer, data, x, z, sx, highZ, minSolidDepth)) {
if (isColumnUnsupported(placer, data, x, z, sx, lowZ, minSolidDepth, projection)
|| isColumnUnsupported(placer, data, x, z, sx, highZ, minSolidDepth, projection)) {
return true;
}
}
for (int sz = lowZ + 1; sz < highZ; sz++) {
if (isColumnUnsupported(placer, data, x, z, lowX, sz, minSolidDepth)
|| isColumnUnsupported(placer, data, x, z, highX, sz, minSolidDepth)) {
if (isColumnUnsupported(placer, data, x, z, lowX, sz, minSolidDepth, projection)
|| isColumnUnsupported(placer, data, x, z, highX, sz, minSolidDepth, projection)) {
return true;
}
}
@@ -214,7 +224,8 @@ public final class IrisSurfaceSupport {
}
private boolean isColumnUnsupported(IObjectPlacer placer, IrisData data, int x, int z,
int stencilX, int stencilZ, int minSolidDepth) {
int stencilX, int stencilZ, int minSolidDepth,
IrisPaintSurfaceProjection projection) {
if (stencilX < 0 || stencilX >= width || stencilZ < 0 || stencilZ >= depth
|| !cells[(stencilX * depth) + stencilZ]) {
return false;
@@ -222,9 +233,14 @@ public final class IrisSurfaceSupport {
int columnX = x + minX + stencilX;
int columnZ = z + minZ + stencilZ;
int surfaceY = placer.getHighest(columnX, columnZ, data, true);
int surfaceY = projection == null ? placer.getHighest(columnX, columnZ, data, true)
: projection.surfaceY(columnX, columnZ);
if (surfaceY == IrisPaintSurfaceProjection.MISSING) {
return false;
}
for (int below = 0; below < minSolidDepth; below++) {
if (placer.isCarved(columnX, surfaceY - below, columnZ)) {
if (placer.isCarved(columnX, surfaceY - below, columnZ)
|| projection != null && !placer.isSurfaceSolid(columnX, surfaceY - below, columnZ)) {
return true;
}
}
@@ -0,0 +1,30 @@
package art.arcane.iris.util.common.director.specialhandlers;
import art.arcane.iris.engine.object.IrisObjectScale;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import art.arcane.volmlib.util.director.handlers.base.DoubleHandlerBase;
public final class ObjectScaleHandler extends DoubleHandlerBase {
@Override
public Double parse(String input, boolean force) throws DirectorParsingException {
if (input.trim().equalsIgnoreCase("dimension")) {
return null;
}
double factor = super.parse(input, force);
try {
return IrisObjectScale.requireValidFactor(factor, "scale");
} catch (IllegalArgumentException failure) {
throw new DirectorParsingException(failure.getMessage());
}
}
@Override
public String toString(Double value) {
return value == null ? "dimension" : super.toString(value);
}
@Override
public String getRandomDefault() {
return "dimension";
}
}