mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-23 16:44:06 +00:00
Whoops Core
This commit is contained in:
1 parent
98e51cba8d
commit
0782bbf0b4
31 files changed
+1178
-190
No files matched your search
+15
-7
@@ -38,6 +38,7 @@ import art.arcane.iris.engine.object.IrisObject;
|
||||
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||
import art.arcane.iris.engine.object.IrisObjectPlacementScaleInterpolator;
|
||||
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||
import art.arcane.iris.engine.object.IrisObjectScale;
|
||||
import art.arcane.iris.engine.object.TileData;
|
||||
import art.arcane.iris.platform.bukkit.BukkitBlockState;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
@@ -51,6 +52,7 @@ import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectHandler;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectScaleHandler;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectTargetHandler;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.math.Direction;
|
||||
@@ -551,17 +553,21 @@ public class CommandObject implements DirectorExecutor {
|
||||
boolean edit,
|
||||
@Param(description = "The amount of degrees to rotate by", descriptionKey = "iris.director.commandobject.param.amount_degrees_rotate_by", defaultValue = "0")
|
||||
int rotate,
|
||||
@Param(description = "The factor by which to scale the object placement", descriptionKey = "iris.director.commandobject.param.factor_by_which_scale_object_placement", defaultValue = "1")
|
||||
double scale
|
||||
@Param(description = "Explicit scale factor, or dimension to inherit the current Iris world's factor", descriptionKey = "iris.director.commandobject.param.factor_by_which_scale_object_placement", defaultValue = "dimension", customHandler = ObjectScaleHandler.class)
|
||||
Double scale
|
||||
// ,
|
||||
// @Param(description = "The scale interpolator to use", descriptionKey = "iris.director.commandobject.param.scale_interpolator_use", defaultValue = "none")
|
||||
// IrisObjectPlacementScaleInterpolator interpolator
|
||||
) {
|
||||
IrisObject o = IrisData.loadAnyObject(object, data());
|
||||
Engine placementEngine = engine();
|
||||
double factor = scale == null
|
||||
? placementEngine == null ? 1D : placementEngine.getDimension().getAllObjectScaleFactor()
|
||||
: IrisObjectScale.requireValidFactor(scale, "scale");
|
||||
double maxScale = Double.max(10 - o.getBlocks().size() / 10000d, 1);
|
||||
if (scale > maxScale) {
|
||||
if (factor > maxScale) {
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_INDICATED_SCALE_EXCEEDS_MAXIMUM_DOWNSCALED_MAXIMUM, MessageArgument.untrusted("maxScale", maxScale)));
|
||||
scale = maxScale;
|
||||
factor = maxScale;
|
||||
}
|
||||
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement();
|
||||
@@ -581,14 +587,16 @@ public class CommandObject implements DirectorExecutor {
|
||||
|
||||
Map<Block, BlockData> futureChanges = new HashMap<>();
|
||||
|
||||
if (scale != 1) {
|
||||
o = o.scaled(scale, IrisObjectPlacementScaleInterpolator.TRICUBIC);
|
||||
if (factor != 1D) {
|
||||
o = scale == null ? IrisObjectScale.getFixed(o, factor)
|
||||
: new IrisObjectScale().setSize(factor)
|
||||
.setInterpolation(IrisObjectPlacementScaleInterpolator.TRICUBIC).get(new RNG(), o);
|
||||
}
|
||||
|
||||
// Block writes must run on the thread owning the target chunk; the undo log stays global.
|
||||
final IrisObject placed = o;
|
||||
if (!J.runAt(block, () -> {
|
||||
placed.place(block.getBlockX(), block.getBlockY() + (int) placed.getCenter().getY(), block.getBlockZ(), createPlacer(block.getWorld(), futureChanges, null), placement, new RNG(), null);
|
||||
placed.place(block.getBlockX(), block.getBlockY() + (int) placed.getCenter().getY(), block.getBlockZ(), createPlacer(block.getWorld(), futureChanges, null), placement, new RNG(), placed.getLoader());
|
||||
J.runGlobal(() -> Iris.service(ObjectSVC.class).addChanges(futureChanges));
|
||||
|
||||
if (!edit) {
|
||||
|
||||
+2
-1
@@ -415,7 +415,8 @@ public final class ModdedObjectCommands {
|
||||
placement.setRotation(IrisObjectRotation.of(0, rotation, 0));
|
||||
ModdedObjectPlacer placer = new ModdedObjectPlacer(level, engine);
|
||||
try {
|
||||
object.place(target.getX(), target.getY() + object.getCenter().getY(), target.getZ(), placer, placement, new RNG(), null);
|
||||
object = placement.scaleObject(new RNG(), object, engine == null ? null : engine.getDimension());
|
||||
object.place(target.getX(), target.getY() + object.getCenter().getY(), target.getZ(), placer, placement, new RNG(), object.getLoader());
|
||||
} catch (Throwable e) {
|
||||
ModdedIrisLog.error("Iris paste failed for {}", key, e);
|
||||
ModdedObjectUndo.record(player == null ? ModdedObjectUndo.CONSOLE : player.getUUID(), level, placer.undoSnapshot());
|
||||
|
||||
@@ -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;
|
||||
|
||||
+8
-2
@@ -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,
|
||||
|
||||
+4
-4
@@ -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!");
|
||||
}
|
||||
|
||||
+17
-15
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class PackObjectScaleFactorTest {
|
||||
@Test
|
||||
public void acceptsOmissionAndFiniteFactorsWithinResamplerLimits() {
|
||||
for (String json : List.of("{}", "{\"allObjectScaleFactor\":0.01}",
|
||||
"{\"allObjectScaleFactor\":0.5}", "{\"allObjectScaleFactor\":1}",
|
||||
"{\"allObjectScaleFactor\":2}", "{\"allObjectScaleFactor\":50}")) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
PackDimensionValidator.validateObjectScaleFactor("main", new JSONObject(json), errors);
|
||||
assertEquals(json, List.of(), errors);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blocksInvalidTypesAndFactorsInPackAndRuntimeValidation() {
|
||||
for (String value : List.of("null", "true", "\"2\"", "[]", "{}", "0", "-1", "0.009", "50.001")) {
|
||||
JSONObject json = new JSONObject("{\"allObjectScaleFactor\":" + value + "}");
|
||||
List<String> errors = new ArrayList<>();
|
||||
PackDimensionValidator.validateObjectScaleFactor("main", json, errors);
|
||||
assertEquals(value, 1, errors.size());
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> PackValidator.requireValidObjectSettings(null, "main", json));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class SchemaBuilderIdentityTest {
|
||||
@ClassRule
|
||||
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "2f47b30c04bf90a5342df92d7335670679176665520527913f3b90ed0cda65d5";
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "ddfc51ba19c02dd50737a0d1ba30ee24cdb6cbbe5fed40c875adac69960ad81d";
|
||||
|
||||
private static final List<Class<?>> SCHEMA_ROOTS = List.of(
|
||||
IrisDimension.class,
|
||||
|
||||
+8
@@ -7,6 +7,14 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisStructureComponentOverboreTest {
|
||||
@Test
|
||||
public void enlargedTreeSearchCoversPaddedScaleWithoutEnlargingShrunkTrees() {
|
||||
assertEquals(4, IrisStructureComponent.scaledMarkerSpan(4, 1D));
|
||||
assertEquals(4, IrisStructureComponent.scaledMarkerSpan(4, 0.5D));
|
||||
assertEquals(8, IrisStructureComponent.scaledMarkerSpan(4, 2D));
|
||||
assertEquals(20, IrisStructureComponent.scaledMarkerSpan(10, 2D));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ordinaryMarkersAreSeparatedFromStructureMarkers() {
|
||||
assertTrue(IrisStructureComponent.isOrdinaryObjectMarker("trees/oak@42"));
|
||||
|
||||
+3
@@ -66,6 +66,7 @@ public class MantleFloatingObjectComponentBoundaryRadiusTest {
|
||||
.setFloatingObjects(new KList<>(placement));
|
||||
IrisBiome biome = new IrisBiome().setFloatingChildBiomes(new KList<>(floatingChild));
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(dimension.getAllObjectScaleFactor()).thenReturn(1D);
|
||||
when(dimension.getReachableBiomes(org.mockito.ArgumentMatchers.any())).thenReturn(new KList<>(biome));
|
||||
|
||||
ResourceLoader<IrisObject> objectLoader = mock(ResourceLoader.class);
|
||||
@@ -80,5 +81,7 @@ public class MantleFloatingObjectComponentBoundaryRadiusTest {
|
||||
when(engineMantle.getData()).thenReturn(data);
|
||||
|
||||
assertEquals(33, new MantleFloatingObjectComponent(engineMantle).getRadius());
|
||||
when(dimension.getAllObjectScaleFactor()).thenReturn(2D);
|
||||
assertEquals(34, new MantleFloatingObjectComponent(engineMantle).getRadius());
|
||||
}
|
||||
}
|
||||
+30
-2
@@ -96,7 +96,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
||||
.setRotation(IrisObjectRotation.of(90, 0, 0))
|
||||
.setTranslate(new IrisObjectTranslate().setX(3).setY(4));
|
||||
|
||||
assertEquals(53, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 48, 7), placement));
|
||||
assertEquals(53, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 48, 7), placement, 1D));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,7 +107,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
||||
.setMode(ObjectPlaceMode.VACUUM)
|
||||
.setVacuumSettings(new IrisVacuumSettings().setRadius(12));
|
||||
|
||||
assertEquals(43, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 4, 3), placement));
|
||||
assertEquals(39, MantleObjectComponent.calculatePlacementReach(new IrisBlockVector(5, 4, 3), placement, placement.getMaximumScale(null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,6 +126,30 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
||||
verify(complex).allowsNewGenerationFootprint(95, 195, 105, 205);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inheritedRadiusMatchesScaledFrameAndExplicitScaleOverridesIt() {
|
||||
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(2D);
|
||||
IrisObjectPlacement inherited = new IrisObjectPlacement();
|
||||
IrisBlockVector size = new IrisBlockVector(8, 8, 8);
|
||||
assertEquals(16, MantleObjectComponent.calculatePlacementReach(size, inherited,
|
||||
inherited.getMaximumScale(dimension)));
|
||||
dimension.setAllObjectScaleFactor(0.5D);
|
||||
assertEquals(4, MantleObjectComponent.calculatePlacementReach(size, inherited,
|
||||
inherited.getMaximumScale(dimension)));
|
||||
inherited.setScale(new IrisObjectScale().setSize(1D));
|
||||
assertEquals(8, MantleObjectComponent.calculatePlacementReach(size, inherited,
|
||||
inherited.getMaximumScale(dimension)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alreadyScaledObjectFootprintDoesNotApplyScaleTwice() {
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisObject object = new IrisObject(20, 20, 20);
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement().setScale(new IrisObjectScale().setSize(2D));
|
||||
MantleObjectComponent.allowsObjectPlacement(complex, object, placement, 100, 200);
|
||||
verify(complex).allowsNewGenerationFootprint(80, 180, 120, 220);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceReplayUsesAscendingSignedCoordinates() {
|
||||
List<String> replayedSources = new ArrayList<>();
|
||||
@@ -185,6 +209,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
||||
when(region.getObjects()).thenReturn(new KList<>());
|
||||
when(region.getProceduralObjects()).thenReturn(proceduralObjects);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(dimension.getAllObjectScaleFactor()).thenReturn(1D);
|
||||
when(dimension.getAllRegions(any())).thenReturn(new KList<>(region));
|
||||
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>());
|
||||
Engine engine = mock(Engine.class);
|
||||
@@ -215,10 +240,12 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
||||
when(upperRegion.getObjects()).thenReturn(new KList<>());
|
||||
when(upperRegion.getProceduralObjects()).thenReturn(proceduralObjects);
|
||||
IrisDimension baseDimension = mock(IrisDimension.class);
|
||||
when(baseDimension.getAllObjectScaleFactor()).thenReturn(1D);
|
||||
when(baseDimension.isUpperDimensionObjects()).thenReturn(true);
|
||||
when(baseDimension.getAllRegions(any())).thenReturn(new KList<>());
|
||||
when(baseDimension.getReachableBiomes(any())).thenReturn(new KList<>());
|
||||
IrisDimension upperDimension = mock(IrisDimension.class);
|
||||
when(upperDimension.getAllObjectScaleFactor()).thenReturn(1D);
|
||||
when(upperDimension.getAllRegions(any())).thenReturn(new KList<>(upperRegion));
|
||||
when(upperDimension.getReachableBiomes(any())).thenReturn(new KList<>());
|
||||
UpperDimensionContext upperContext = mock(UpperDimensionContext.class);
|
||||
@@ -252,6 +279,7 @@ public class MantleObjectComponentBoundaryRadiusTest {
|
||||
.setTranslate(new IrisObjectTranslate().setX(32));
|
||||
IrisBiome biome = new IrisBiome().setObjects(new KList<>(placement));
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(dimension.getAllObjectScaleFactor()).thenReturn(1D);
|
||||
when(dimension.isUseMantle()).thenReturn(true);
|
||||
when(dimension.getAllRegions(any())).thenReturn(new KList<>());
|
||||
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>(biome));
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisObjectGlobalScaleTest {
|
||||
@Rule
|
||||
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@After
|
||||
public void clearCache() {
|
||||
IrisObjectScale.invalidate(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultFactorPreservesObjectAndRandomSequence() {
|
||||
IrisObject origin = new IrisObject(8, 8, 8);
|
||||
IrisObjectPlacement placement = gson.fromJson("{}", IrisObjectPlacement.class);
|
||||
RNG actual = new RNG(731L);
|
||||
RNG expected = new RNG(731L);
|
||||
|
||||
assertNull(placement.getScale());
|
||||
assertSame(origin, placement.scaleObject(actual, origin, new IrisDimension()));
|
||||
assertSame(origin, placement.scaleObject(actual, origin, null));
|
||||
assertEquals(expected.nextLong(), actual.nextLong());
|
||||
assertFalse(gson.toJson(placement).contains("\"scale\""));
|
||||
assertNull(placement.toPlacement("trees/oak").getScale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dimensionFactorsUseSeparateCachedVariantsWithoutChangingSourceOrRandomness() {
|
||||
IrisObject origin = new IrisObject(8, 8, 8);
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement();
|
||||
IrisDimension large = new IrisDimension().setAllObjectScaleFactor(2D);
|
||||
IrisDimension small = new IrisDimension().setAllObjectScaleFactor(0.5D);
|
||||
RNG actual = new RNG(731L);
|
||||
RNG expected = new RNG(731L);
|
||||
|
||||
IrisObject doubled = placement.scaleObject(actual, origin, large);
|
||||
IrisObject halved = placement.scaleObject(actual, origin, small);
|
||||
|
||||
assertEquals(16, doubled.getW());
|
||||
assertEquals(4, halved.getW());
|
||||
assertEquals(8, origin.getW());
|
||||
assertNotSame(origin, doubled);
|
||||
assertNotSame(doubled, halved);
|
||||
assertSame(doubled, placement.scaleObject(actual, origin, large));
|
||||
assertSame(halved, placement.scaleObject(actual, origin, small));
|
||||
assertEquals(expected.nextLong(), actual.nextLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void globalAndExplicitFactorsScaleOccupiedCubeBoundsExactly() {
|
||||
IrisObject origin = new IrisObject(4, 4, 4);
|
||||
PlatformBlockState stone = mock(PlatformBlockState.class);
|
||||
for (int x = 0; x < 4; x++) {
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int z = 0; z < 4; z++) {
|
||||
origin.setUnsigned(x, y, z, stone);
|
||||
}
|
||||
}
|
||||
}
|
||||
IrisObjectPlacement inherited = new IrisObjectPlacement();
|
||||
IrisDimension doubled = new IrisDimension().setAllObjectScaleFactor(2D);
|
||||
assertOccupiedCube(inherited.scaleObject(new RNG(1), origin, doubled), 8);
|
||||
assertOccupiedCube(inherited.scaleObject(new RNG(1), origin,
|
||||
new IrisDimension().setAllObjectScaleFactor(0.5D)), 2);
|
||||
assertOccupiedCube(new IrisObjectPlacement().setScale(new IrisObjectScale().setSize(0.5D))
|
||||
.scaleObject(new RNG(1), origin, doubled), 2);
|
||||
assertOccupiedCube(new IrisObjectPlacement().setScale(new IrisObjectScale())
|
||||
.scaleObject(new RNG(1), origin, doubled), 4);
|
||||
assertOccupiedCube(origin, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledVariationRangeReportsTheUnscaledFootprint() {
|
||||
IrisObject origin = new IrisObject(4, 4, 4);
|
||||
IrisObjectScale scale = new IrisObjectScale().setMinimumScale(0.25D)
|
||||
.setMaximumScale(0.25D).setVariations(0);
|
||||
assertSame(origin, scale.get(new RNG(1), origin));
|
||||
assertEquals(1D, scale.getMaxScale(), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitUnitAndEmptyScaleOverrideDimensionFactorAcrossJsonCopies() {
|
||||
IrisObject origin = new IrisObject(8, 8, 8);
|
||||
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(2D);
|
||||
for (String json : new String[]{"{\"scale\":{}}", "{\"scale\":{\"size\":1.0}}"}) {
|
||||
IrisObjectPlacement placement = gson.fromJson(json, IrisObjectPlacement.class);
|
||||
IrisObjectPlacement copy = gson.fromJson(gson.toJson(placement.toPlacement("tree")), IrisObjectPlacement.class);
|
||||
|
||||
assertSame(origin, copy.scaleObject(new RNG(1), origin, dimension));
|
||||
assertEquals(1D, copy.getMaximumScale(dimension), 0D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void referencedScaleSnippetRemainsAnExplicitOverride() throws Exception {
|
||||
Path root = temporaryFolder.getRoot().toPath();
|
||||
Path snippets = Files.createDirectories(root.resolve("snippet/object-scale"));
|
||||
Files.writeString(snippets.resolve("unit.json"), "{}");
|
||||
Files.writeString(snippets.resolve("half.json"), "{\"size\":0.5}");
|
||||
IrisData data = mock(IrisData.class, CALLS_REAL_METHODS);
|
||||
when(data.getDataFolder()).thenReturn(root.toFile());
|
||||
Gson loaderGson = new GsonBuilder().registerTypeAdapterFactory(data).create();
|
||||
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(2D);
|
||||
IrisObject origin = new IrisObject(4, 4, 4);
|
||||
IrisObjectPlacement unit = loaderGson.fromJson(
|
||||
"{\"scale\":\"snippet/object-scale/unit\"}", IrisObjectPlacement.class);
|
||||
IrisObjectPlacement half = loaderGson.fromJson(
|
||||
"{\"scale\":\"snippet/object-scale/half\"}", IrisObjectPlacement.class);
|
||||
|
||||
assertNotNull(unit.getScale());
|
||||
assertSame(origin, unit.scaleObject(new RNG(1), origin, dimension));
|
||||
assertEquals(2, half.scaleObject(new RNG(1), origin, dimension).getW());
|
||||
IrisObjectPlacement copied = loaderGson.fromJson(loaderGson.toJson(half.toPlacement("tree")),
|
||||
IrisObjectPlacement.class);
|
||||
assertEquals(0.5D, copied.getMaximumScale(dimension), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitRangeKeepsItsExistingSelectionSequenceWithoutMultiplying() {
|
||||
IrisObject origin = new IrisObject(8, 8, 8);
|
||||
IrisObjectScale authored = new IrisObjectScale().setMinimumScale(0.5D)
|
||||
.setMaximumScale(1.5D).setVariations(4);
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement().setScale(authored);
|
||||
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(3D);
|
||||
RNG actual = new RNG(1337);
|
||||
RNG expected = new RNG(1337);
|
||||
|
||||
for (int index = 0; index < 12; index++) {
|
||||
assertSame(authored.get(expected, origin), placement.scaleObject(actual, origin, dimension));
|
||||
}
|
||||
assertEquals(1.5D, placement.getMaximumScale(dimension), 0D);
|
||||
assertEquals(expected.nextLong(), actual.nextLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitOppositeFactorWinsAndDirectSettersRejectUnsafeValues() {
|
||||
IrisObject origin = new IrisObject(8, 8, 8);
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement().setScale(new IrisObjectScale().setSize(0.5D));
|
||||
assertSame(IrisObjectScale.getFixed(origin, 0.5D), placement.scaleObject(new RNG(1), origin,
|
||||
new IrisDimension().setAllObjectScaleFactor(2D)));
|
||||
for (double invalid : new double[]{Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,
|
||||
0D, -1D, 0.009D, 50.001D}) {
|
||||
assertThrows(IllegalArgumentException.class, () -> new IrisDimension().setAllObjectScaleFactor(invalid));
|
||||
assertThrows(IllegalArgumentException.class, () -> new IrisObjectScale().setSize(invalid));
|
||||
assertThrows(IllegalArgumentException.class, () -> new IrisStaticObject().setScale(invalid));
|
||||
}
|
||||
assertThrows(IllegalArgumentException.class, () -> gson.fromJson(
|
||||
"{\"allObjectScaleFactor\":0}", IrisDimension.class).getAllObjectScaleFactor());
|
||||
}
|
||||
|
||||
private static void assertOccupiedCube(IrisObject object, int width) {
|
||||
assertEquals(width, object.getW());
|
||||
assertEquals(width, object.getH());
|
||||
assertEquals(width, object.getD());
|
||||
assertEquals(width * width * width, object.getBlocks().size());
|
||||
int minimum = -(width / 2);
|
||||
for (int x = minimum; x < minimum + width; x++) {
|
||||
for (int y = minimum; y < minimum + width; y++) {
|
||||
for (int z = minimum; z < minimum + width; z++) {
|
||||
assertNotNull(object.getBlocks().get(new IrisBlockVector(x, y, z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(minimum, object.getAABB().min().getX());
|
||||
assertEquals(minimum + width - 1, object.getAABB().max().getX());
|
||||
}
|
||||
|
||||
}
|
||||
+159
-2
@@ -2,12 +2,14 @@ package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.NativeStructureVolume;
|
||||
import art.arcane.iris.engine.framework.PlacedObject;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
@@ -37,7 +39,9 @@ import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -279,6 +283,133 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
||||
assertEquals(1, object.getStates().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paintFollowsTheLowerLedgeBeneathADistantRoof() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.terrain(1, 0, 0, 80, 150, 160);
|
||||
placer.terrain(2, 0, 0, 80, 150, 160);
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT)
|
||||
.setRequireSurfaceSupport(true).setSurfaceSupportBuffer(0);
|
||||
|
||||
assertEquals(80, lineObject(5).place(0, -1, 0, placer, placement, new RNG(2L), data));
|
||||
|
||||
assertEquals(5, placer.writes().size());
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.y() == 80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paintClipsAtACliffWithoutReachingThePatchBeyondIt() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.terrain(1, 0, 0, 20);
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT);
|
||||
|
||||
lineObject(5).place(0, -1, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
assertEquals(3, placer.writes().size());
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.x() <= 0 && write.y() == 80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paintAccumulatesNormalAscendingAndDescendingSlopeSteps() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
for (int x = -6; x <= 6; x++) {
|
||||
placer.terrain(x, 0, 0, 80 + x * 3);
|
||||
}
|
||||
|
||||
lineObject(13).place(0, -1, 0, placer, placement().setMode(ObjectPlaceMode.PAINT), new RNG(2L), data);
|
||||
|
||||
assertEquals(13, placer.writes().size());
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.y() == 80 + write.x() * 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paintingAndBoringCannotFeedBackIntoTheSurfaceProjection() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.highestFollowsWrites = true;
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT).setBore(true);
|
||||
|
||||
boxObject(1, 3, 1).place(0, -1, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
assertEquals(3, placer.writesOf(solid).size());
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.y() >= 80 && write.y() <= 82));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paintUsesTranslatedAnchorAndMatchingRandomizedBoreBounds() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.terrain(0, -3, 0, 108);
|
||||
placer.terrain(0, -4, 0, 110);
|
||||
placer.terrain(0, -5, 0, 112);
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT).setBore(true)
|
||||
.setRotation(IrisObjectRotation.of(0, 90, 0))
|
||||
.setTranslate(new IrisObjectTranslate().setX(4).setY(5).setYRandom(3));
|
||||
RNG rng = mock(RNG.class);
|
||||
when(rng.i(0, 3)).thenReturn(3);
|
||||
|
||||
assertEquals(113, lineObject(3).place(0, -1, 0, placer, placement, rng, data));
|
||||
|
||||
assertEquals(3, placer.writesOf(solid).size());
|
||||
assertEquals(3, placer.writesOf(IrisObject.States.air()).size());
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.x() == 0
|
||||
&& write.z() >= -5 && write.z() <= -3 && write.y() == 110 - (write.z() + 4) * 2 + 8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeVetoUsesTheSelectedLedgeAndRandomTranslationOffset() {
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
placer.terrain(1, 0, 0, 80, 150, 160);
|
||||
when(engine.getMinHeight()).thenReturn(-64);
|
||||
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(
|
||||
new KList<>(NativeStructureVolume.of("test", 1, 21, 0, 1, 21, 0)));
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT).setForcePlace(true)
|
||||
.setTranslate(new IrisObjectTranslate().setY(2).setYRandom(3));
|
||||
RNG rng = mock(RNG.class);
|
||||
when(rng.i(0, 3)).thenReturn(3);
|
||||
|
||||
assertEquals(-1, lineObject(3).place(0, -1, 0, placer, placement, rng, data));
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
|
||||
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(
|
||||
new KList<>(NativeStructureVolume.of("test", 1, 101, 0, 1, 101, 0)));
|
||||
assertEquals(83, lineObject(3).place(0, -1, 0, placer, placement, rng, data));
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.y() == 85));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void warpedPaintAndNativeVetoUseTheSameFinalColumns() {
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
for (int x = 1; x <= 3; x++) {
|
||||
placer.terrain(x, 2, 0, 80, 150, 160);
|
||||
}
|
||||
IrisObjectPlacement placement = spy(placement().setMode(ObjectPlaceMode.PAINT).setForcePlace(true)
|
||||
.setWarp(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).setMultiplier(4)));
|
||||
CNG warp = mock(CNG.class);
|
||||
when(warp.fitDouble(anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyDouble())).thenReturn(2D);
|
||||
doReturn(warp).when(placement).getSurfaceWarp(any(RNG.class), any());
|
||||
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(
|
||||
new KList<>(NativeStructureVolume.of("test", 3, 80, 2, 3, 80, 2)));
|
||||
|
||||
assertEquals(-1, lineObject(3).place(0, -1, 0, placer, placement, new RNG(2L), data));
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
|
||||
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(NativeStructureVolume.NONE);
|
||||
lineObject(3).place(0, -1, 0, placer, placement, new RNG(2L), data);
|
||||
assertEquals(3, placer.writes().size());
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.x() >= 1 && write.x() <= 3
|
||||
&& write.z() == 2 && write.y() == 80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paintSupportChecksTheThicknessOfTheSelectedLowerLedge() {
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.terrain(1, 0, 0, 20, 80, 80, 150, 160);
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.PAINT)
|
||||
.setRequireSurfaceSupport(true).setSurfaceSupportBuffer(0).setSurfaceSupportDepth(2);
|
||||
|
||||
assertEquals(-1, lineObject(3).place(0, -1, 0, placer, placement, new RNG(2L), data));
|
||||
assertTrue(placer.writes().isEmpty());
|
||||
}
|
||||
|
||||
private IrisObjectPlacement placement() {
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement();
|
||||
placement.setMode(ObjectPlaceMode.CENTER_HEIGHT);
|
||||
@@ -326,9 +457,11 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
||||
private final Map<String, PlatformBlockState> world = new HashMap<>();
|
||||
private final Map<String, Object> data = new HashMap<>();
|
||||
private final List<String> sampledColumns = new ArrayList<>();
|
||||
private final Map<String, int[]> terrain = new HashMap<>();
|
||||
private final Engine engine;
|
||||
private int failAfterWrites = Integer.MAX_VALUE;
|
||||
private boolean debugSmartBore;
|
||||
private boolean highestFollowsWrites;
|
||||
|
||||
private RecordingPlacer(Engine engine) {
|
||||
this.engine = engine;
|
||||
@@ -342,6 +475,16 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
||||
this.debugSmartBore = debugSmartBore;
|
||||
}
|
||||
|
||||
private void terrain(int x, int z, int... spans) {
|
||||
terrain.put(x + ":" + z, spans);
|
||||
}
|
||||
|
||||
private int highest(int x, int z) {
|
||||
int[] spans = terrain.get(x + ":" + z);
|
||||
return (spans == null ? ANCHOR_Y : spans[spans.length - 1])
|
||||
+ (highestFollowsWrites && !writes.isEmpty() ? 1000 : 0);
|
||||
}
|
||||
|
||||
private List<BlockWrite> writes() {
|
||||
return writes;
|
||||
}
|
||||
@@ -357,13 +500,13 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
||||
@Override
|
||||
public int getHighest(int x, int z, IrisData data) {
|
||||
sampledColumns.add(x + ":" + z);
|
||||
return ANCHOR_Y;
|
||||
return highest(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) {
|
||||
sampledColumns.add(x + ":" + z);
|
||||
return ANCHOR_Y;
|
||||
return highest(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -390,6 +533,20 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSurfaceSolid(int x, int y, int z) {
|
||||
int[] spans = terrain.get(x + ":" + z);
|
||||
if (spans == null) {
|
||||
return y >= 0 && y <= ANCHOR_Y;
|
||||
}
|
||||
for (int index = 0; index < spans.length; index += 2) {
|
||||
if (y >= spans[index] && y <= spans[index + 1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSolid(int x, int y, int z) {
|
||||
PlatformBlockState state = get(x, y, z);
|
||||
|
||||
@@ -215,6 +215,56 @@ public class IrisObjectTransformsTileTest {
|
||||
assertEquals("minecraft:barrel", scaled.getBlocks().get(new IrisBlockVector(4, 0, 0)).materialKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalReductionIncludesTheOuterSavedVoxelEdge() {
|
||||
IrisObject source = new IrisObject(4, 1, 1);
|
||||
source.setUnsigned(3, 0, 0, state("minecraft:chest"));
|
||||
source.setUnsignedTile(3, 0, 0, tile("minecraft:chest", "edge"));
|
||||
|
||||
IrisObject scaled = source.scaledAroundOrigin(0.75D, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
|
||||
assertEquals(2, scaled.getBlocks().size());
|
||||
assertEquals("minecraft:chest", scaled.getBlocks().get(new IrisBlockVector(1, 0, 0)).materialKey());
|
||||
assertEquals("edge", scaled.getStates().get(new IrisBlockVector(1, 0, 0)).getProperties().get("name"));
|
||||
assertEquals(1, scaled.getAABB().max().getX());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalScaleWinnersDoNotDependOnSourceInsertionOrder() {
|
||||
for (boolean savedOrigin : new boolean[]{false, true}) {
|
||||
IrisObject ascending = collisionSource(false);
|
||||
IrisObject descending = collisionSource(true);
|
||||
IrisObject first = savedOrigin
|
||||
? ascending.scaledAroundOrigin(0.5D, IrisObjectPlacementScaleInterpolator.NONE)
|
||||
: ascending.scaled(0.5D, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
IrisObject second = savedOrigin
|
||||
? descending.scaledAroundOrigin(0.5D, IrisObjectPlacementScaleInterpolator.NONE)
|
||||
: descending.scaled(0.5D, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
|
||||
assertEquals(2, first.getBlocks().size());
|
||||
assertEquals(1, first.getStates().size());
|
||||
assertEquals("minecraft:stone", first.getBlocks().get(new IrisBlockVector(-1, 0, 0)).materialKey());
|
||||
assertEquals("winner", first.getStates().get(new IrisBlockVector(0, 0, 0)).getProperties().get("name"));
|
||||
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : first.getBlocks()) {
|
||||
assertEquals(entry.getValue().materialKey(), second.getBlocks().get(entry.getKey()).materialKey());
|
||||
assertEquals(first.getStates().get(entry.getKey()), second.getStates().get(entry.getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisObject collisionSource(boolean reverse) {
|
||||
IrisObject source = new IrisObject(4, 1, 1);
|
||||
for (int index = 0; index < 4; index++) {
|
||||
int x = reverse ? 3 - index : index;
|
||||
String material = x == 1 ? "minecraft:stone" : "minecraft:chest";
|
||||
source.setUnsigned(x, 0, 0, state(material));
|
||||
if (x != 1) {
|
||||
source.setUnsignedTile(x, 0, 0, tile(material, x == 3 ? "winner" : "overwritten"));
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void untypedTilesRemainAttachedToTheirScaledSourceMaterial() {
|
||||
IrisObject source = new IrisObject(1, 1, 1);
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisPaintSurfaceProjectionTest {
|
||||
@Test
|
||||
public void positiveAndNegativeChunkSeamsHaveIdenticalRelativeSurfaces() {
|
||||
IrisPaintSurfaceProjection positive = projection(15);
|
||||
IrisPaintSurfaceProjection negative = projection(-17);
|
||||
assertNotNull(positive);
|
||||
assertNotNull(negative);
|
||||
|
||||
for (int x = -5; x <= 5; x++) {
|
||||
for (int z = -1; z <= 1; z++) {
|
||||
assertEquals(80 + x * 2, positive.surfaceY(15 + x, z));
|
||||
assertEquals(positive.surfaceY(15 + x, z), negative.surfaceY(-17 + x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exposedFloorsMustExistAtTheRequestedAnchor() {
|
||||
IObjectPlacer placer = mock(IObjectPlacer.class);
|
||||
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean())).thenReturn(140);
|
||||
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
int y = invocation.getArgument(1);
|
||||
return y >= 0 && y <= 100;
|
||||
});
|
||||
|
||||
assertNull(IrisPaintSurfaceProjection.create(placer, request(80, false)));
|
||||
assertNotNull(IrisPaintSurfaceProjection.create(placer, request(100, false)));
|
||||
assertNull(IrisPaintSurfaceProjection.create(placer, request(140, false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aVirtualHeightmapDoesNotFallBackToPhysicalLedges() {
|
||||
IObjectPlacer placer = mock(IObjectPlacer.class);
|
||||
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean()))
|
||||
.thenAnswer(invocation -> (int) invocation.getArgument(0) == 0 ? 80 : 140);
|
||||
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt()))
|
||||
.thenAnswer(invocation -> (int) invocation.getArgument(1) <= 80);
|
||||
IrisPaintSurfaceProjection projection = IrisPaintSurfaceProjection.create(placer, request(80, true));
|
||||
|
||||
assertNotNull(projection);
|
||||
assertEquals(80, projection.surfaceY(0, 0));
|
||||
assertEquals(IrisPaintSurfaceProjection.MISSING, projection.surfaceY(1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceChoicesStayCompatibleWhenTwoTraversalBranchesMeet() {
|
||||
IObjectPlacer placer = mock(IObjectPlacer.class);
|
||||
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean())).thenAnswer(invocation -> {
|
||||
int x = invocation.getArgument(0);
|
||||
int z = invocation.getArgument(1);
|
||||
return 80 + x * z;
|
||||
});
|
||||
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
int x = invocation.getArgument(0);
|
||||
int y = invocation.getArgument(1);
|
||||
int z = invocation.getArgument(2);
|
||||
return y <= 80 + x * z;
|
||||
});
|
||||
IrisPaintSurfaceProjection projection = IrisPaintSurfaceProjection.create(placer,
|
||||
new IrisPaintSurfaceProjection.Request(null, -6, 6, -6, 6, 0, 80, 0, true, false));
|
||||
assertNotNull(projection);
|
||||
|
||||
for (int x = -6; x < 6; x++) {
|
||||
for (int z = -6; z < 6; z++) {
|
||||
int surface = projection.surfaceY(x, z);
|
||||
if (surface == IrisPaintSurfaceProjection.MISSING) {
|
||||
continue;
|
||||
}
|
||||
int east = projection.surfaceY(x + 1, z);
|
||||
int south = projection.surfaceY(x, z + 1);
|
||||
assertTrue(east == IrisPaintSurfaceProjection.MISSING || Math.abs(surface - east) <= 4);
|
||||
assertTrue(south == IrisPaintSurfaceProjection.MISSING || Math.abs(surface - south) <= 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IrisPaintSurfaceProjection projection(int anchorX) {
|
||||
IObjectPlacer placer = mock(IObjectPlacer.class);
|
||||
when(placer.getHighest(anyInt(), anyInt(), any(), anyBoolean())).thenAnswer(invocation -> {
|
||||
int x = invocation.getArgument(0);
|
||||
return x > anchorX ? 200 : 80 + (x - anchorX) * 2;
|
||||
});
|
||||
when(placer.isSurfaceSolid(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
int x = invocation.getArgument(0);
|
||||
int y = invocation.getArgument(1);
|
||||
return y <= 80 + (x - anchorX) * 2 || x > anchorX && y >= 190 && y <= 200;
|
||||
});
|
||||
return IrisPaintSurfaceProjection.create(placer, new IrisPaintSurfaceProjection.Request(null,
|
||||
anchorX - 5, anchorX + 5, -1, 1, anchorX, 80, 0, true, false));
|
||||
}
|
||||
|
||||
private IrisPaintSurfaceProjection.Request request(int anchorY, boolean virtual) {
|
||||
return new IrisPaintSurfaceProjection.Request(null, 0, 1, 0, 0, 0, anchorY, 0, true, virtual);
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,41 @@ public class IrisStaticObjectLayerTest {
|
||||
verifyNoInteractions(engine, loader);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void globalScalingKeepsSavedOriginsAndExplicitEntrySizes() {
|
||||
IrisObject cube = object("cube", 4, 4, 4);
|
||||
for (int x = -2; x < 2; x++) {
|
||||
for (int y = -2; y < 2; y++) {
|
||||
for (int z = -2; z < 2; z++) {
|
||||
cube.getBlocks().put(new IrisBlockVector(x, y, z), state("minecraft:stone"));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (double factor : new double[]{2D, 0.5D}) {
|
||||
IrisDimension dimension = new IrisDimension().setAllObjectScaleFactor(factor)
|
||||
.setStaticObjects(new KList<>(entry("cube", 16, 100, 16),
|
||||
entry("cube", 48, 100, 16).setScale(1D),
|
||||
entry("cube", 80, 100, 16).setScale(1D / factor)));
|
||||
IrisStaticObjectLayer layer = dimension.getStaticObjectLayer(data);
|
||||
int[] counts = new int[3];
|
||||
for (int chunkX = 0; chunkX < 6; chunkX++) {
|
||||
for (int chunkZ = 0; chunkZ < 2; chunkZ++) {
|
||||
counts[chunkX / 2] += layer.blocks(chunkX, chunkZ).size();
|
||||
}
|
||||
}
|
||||
assertEquals(factor == 2D ? 512 : 8, counts[0]);
|
||||
assertEquals(64, counts[1]);
|
||||
assertEquals(factor == 2D ? 8 : 512, counts[2]);
|
||||
int halfWidth = factor == 2D ? 4 : 1;
|
||||
assertTrue(layer.contains(16 - halfWidth, 164 - halfWidth, 16 - halfWidth));
|
||||
assertFalse(layer.contains(15 - halfWidth, 164 - halfWidth, 16 - halfWidth));
|
||||
dimension.setAllObjectScaleFactor(1D);
|
||||
assertNotSame(layer, dimension.getStaticObjectLayer(data));
|
||||
}
|
||||
assertEquals(64, cube.getBlocks().size());
|
||||
assertEquals(4, cube.getW());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rotatedNegativeCoordinatesAndMinusOneYUseExactWorldOrigin() {
|
||||
IrisObject object = object("cross", 5, 3, 5);
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.List;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -23,7 +24,7 @@ public class IrisStaticObjectTest {
|
||||
assertEquals(ObjectPlaceMode.STRUCTURE_PIECE, placement.getMode());
|
||||
assertTrue(placement.isForcePlace());
|
||||
assertFalse(placement.getRotation().isEnabled());
|
||||
assertFalse(placement.getScale().shouldScale());
|
||||
assertNull(placement.getScale());
|
||||
assertEquals(new IrisPosition(100, 100, -100), object.getPosition());
|
||||
}
|
||||
|
||||
@@ -69,8 +70,8 @@ public class IrisStaticObjectTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> new IrisStaticObject().validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class, () -> validObject().setPosition(null).validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(Double.NaN).validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(0).validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(51).validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(0D).validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class, () -> validObject().setScale(51D).validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class, () -> validObject().setRotation(null).validate(-64, 320));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> validObject().setRotation(new IrisStaticObjectRotation().setY(-361)).validate(-64, 320));
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package art.arcane.iris.util.common.director.specialhandlers;
|
||||
|
||||
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class ObjectScaleHandlerTest {
|
||||
@Test
|
||||
public void distinguishesInheritedScaleFromExplicitOne() throws Exception {
|
||||
ObjectScaleHandler handler = new ObjectScaleHandler();
|
||||
assertNull(handler.parse("dimension", true));
|
||||
assertNull(handler.parse(" DIMENSION ", false));
|
||||
assertEquals(1D, handler.parse("1.0", false), 0D);
|
||||
assertEquals(0.5D, handler.parse("0.5", false), 0D);
|
||||
assertEquals(2D, handler.parse("2", false), 0D);
|
||||
assertEquals("dimension", handler.toString(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsInvalidManualFactors() {
|
||||
ObjectScaleHandler handler = new ObjectScaleHandler();
|
||||
for (String invalid : new String[]{"NaN", "Infinity", "-Infinity", "0", "-1", "0.009", "50.001"}) {
|
||||
assertThrows(invalid, DirectorParsingException.class, () -> handler.parse(invalid, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user