mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-18 23:50:35 +00:00
Wip floaters
This commit is contained in:
@@ -7,7 +7,7 @@ The master branch is for the latest version of minecraft.
|
||||
# Building
|
||||
|
||||
Building Iris is fairly simple, though you will need to setup a few things if your system has never been used for java
|
||||
development.
|
||||
development.[README.md](README.md)
|
||||
|
||||
Consider supporting our development by buying Iris on spigot! We work hard to make Iris the best it can be for everyone.
|
||||
|
||||
|
||||
+35
-9
@@ -126,7 +126,13 @@ public class IslandObjectPlacer implements IObjectPlacer {
|
||||
}
|
||||
|
||||
private boolean shouldSkipAirColumn(int x, int y, int z) {
|
||||
writesAttempted++;
|
||||
return shouldSkipAirColumn(x, y, z, true);
|
||||
}
|
||||
|
||||
private boolean shouldSkipAirColumn(int x, int y, int z, boolean countWrite) {
|
||||
if (countWrite) {
|
||||
writesAttempted++;
|
||||
}
|
||||
int xf = x - minX;
|
||||
int zf = z - minZ;
|
||||
if (xf >= 0 && xf < 16 && zf >= 0 && zf < 16) {
|
||||
@@ -136,27 +142,37 @@ public class IslandObjectPlacer implements IObjectPlacer {
|
||||
return false;
|
||||
}
|
||||
if (y >= anchorY) {
|
||||
writesDroppedAboveBottom++;
|
||||
if (countWrite) {
|
||||
writesDroppedAboveBottom++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (face == AnchorFace.TOP) {
|
||||
if (y <= anchorY) {
|
||||
writesDroppedBelow++;
|
||||
if (countWrite) {
|
||||
writesDroppedBelow++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!overhangAllowed[idx]) {
|
||||
writesDroppedOverhang++;
|
||||
if (countWrite) {
|
||||
writesDroppedOverhang++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (y >= anchorY) {
|
||||
writesDroppedBottomOverhang++;
|
||||
if (countWrite) {
|
||||
writesDroppedBottomOverhang++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!overhangAllowed[idx]) {
|
||||
writesDroppedBottomOverhang++;
|
||||
if (countWrite) {
|
||||
writesDroppedBottomOverhang++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -164,19 +180,29 @@ public class IslandObjectPlacer implements IObjectPlacer {
|
||||
}
|
||||
if (face == AnchorFace.TOP) {
|
||||
if (y <= anchorY) {
|
||||
writesDroppedBelow++;
|
||||
if (countWrite) {
|
||||
writesDroppedBelow++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (y >= anchorY) {
|
||||
writesDroppedBottomOverhang++;
|
||||
if (countWrite) {
|
||||
writesDroppedBottomOverhang++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
writesDroppedOverhang++;
|
||||
if (countWrite) {
|
||||
writesDroppedOverhang++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean canWriteObjectBlock(int x, int y, int z) {
|
||||
return !shouldSkipAirColumn(x, y, z, false);
|
||||
}
|
||||
|
||||
private @Nullable FloatingIslandSample sampleAt(int x, int z) {
|
||||
int xf = x - minX;
|
||||
int zf = z - minZ;
|
||||
|
||||
+133
-97
@@ -29,6 +29,7 @@ import art.arcane.iris.engine.mantle.MantleWriter;
|
||||
import art.arcane.iris.engine.modifier.IrisFloatingChildBiomeModifier;
|
||||
import art.arcane.iris.engine.object.FloatingIslandSample;
|
||||
import art.arcane.iris.engine.object.FloatingObjectFootprint;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
||||
import art.arcane.iris.engine.object.IrisObject;
|
||||
@@ -36,12 +37,23 @@ import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||
import art.arcane.iris.engine.object.IrisObjectTranslate;
|
||||
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.util.BlockVector;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@ComponentFlag(ReservedFlag.FLOATING_OBJECT)
|
||||
@@ -53,7 +65,6 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
public static final AtomicLong objectsRelaxed = new AtomicLong();
|
||||
public static final AtomicLong objectsSkippedShrink = new AtomicLong();
|
||||
public static final AtomicLong objectsSkippedNullObj = new AtomicLong();
|
||||
public static final AtomicLong terrainMismatchWarnings = new AtomicLong();
|
||||
public static final AtomicLong writesAttemptedTotal = new AtomicLong();
|
||||
public static final AtomicLong writesDroppedBelowTotal = new AtomicLong();
|
||||
public static final AtomicLong writesDroppedOverhangTotal = new AtomicLong();
|
||||
@@ -65,13 +76,10 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
public static final AtomicLong objectsInvertedSkippedNullObj = new AtomicLong();
|
||||
public static final AtomicLong writesDroppedAboveBottomTotal = new AtomicLong();
|
||||
public static final AtomicLong writesDroppedBottomOverhangTotal = new AtomicLong();
|
||||
private static final int TERRAIN_MISMATCH_WARNING_CAP = 200;
|
||||
private static final AtomicLong heavyClipWarnings = new AtomicLong();
|
||||
private static final int HEAVY_CLIP_WARNING_CAP = 30;
|
||||
private static final double HEAVY_CLIP_RATIO = 0.5;
|
||||
private static final int MIN_FOOTPRINT_CELLS_CHECKED = 3;
|
||||
private static final int INVERTED_PICK_ATTEMPTS = 8;
|
||||
private static final IrisObjectRotation ROTATION_NONE = IrisObjectRotation.of(0, 0, 0);
|
||||
public static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> anchorYHisto = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
public static final ConcurrentHashMap<String, AtomicLong> anchorYHisto = new ConcurrentHashMap<>();
|
||||
|
||||
public MantleFloatingObjectComponent(EngineMantle engineMantle) {
|
||||
super(engineMantle, ReservedFlag.FLOATING_OBJECT, 2);
|
||||
@@ -85,11 +93,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
objectsRelaxed.set(0);
|
||||
objectsSkippedShrink.set(0);
|
||||
objectsSkippedNullObj.set(0);
|
||||
terrainMismatchWarnings.set(0);
|
||||
writesAttemptedTotal.set(0);
|
||||
writesDroppedBelowTotal.set(0);
|
||||
writesDroppedOverhangTotal.set(0);
|
||||
heavyClipWarnings.set(0);
|
||||
anchorYHisto.clear();
|
||||
objectsInvertedAttempted.set(0);
|
||||
objectsInvertedPlaced.set(0);
|
||||
@@ -101,26 +107,13 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
writesDroppedBottomOverhangTotal.set(0);
|
||||
}
|
||||
|
||||
private static void recordWriteStats(IrisObject obj, int wx, int wz, int pickTopY, IslandObjectPlacer islandPlacer) {
|
||||
private static void recordWriteStats(IslandObjectPlacer islandPlacer) {
|
||||
int attempted = islandPlacer.getWritesAttempted();
|
||||
int below = islandPlacer.getWritesDroppedBelow();
|
||||
int overhang = islandPlacer.getWritesDroppedOverhang();
|
||||
writesAttemptedTotal.addAndGet(attempted);
|
||||
writesDroppedBelowTotal.addAndGet(below);
|
||||
writesDroppedOverhangTotal.addAndGet(overhang);
|
||||
int dropped = below + overhang;
|
||||
if (attempted >= 32 && dropped >= attempted * HEAVY_CLIP_RATIO) {
|
||||
long warned = heavyClipWarnings.get();
|
||||
if (warned < HEAVY_CLIP_WARNING_CAP && heavyClipWarnings.incrementAndGet() <= HEAVY_CLIP_WARNING_CAP) {
|
||||
String objKey = obj == null ? "<null>" : obj.getLoadKey();
|
||||
Iris.warn("[FloatingWriteClip] object=" + objKey
|
||||
+ " at=(" + wx + "," + (pickTopY + 1) + "," + wz + ")"
|
||||
+ " attempted=" + attempted
|
||||
+ " droppedBelow=" + below
|
||||
+ " droppedOverhang=" + overhang
|
||||
+ " written=" + (attempted - dropped));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void recordInvertedWriteStats(IslandObjectPlacer islandPlacer) {
|
||||
@@ -128,34 +121,6 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
writesDroppedBottomOverhangTotal.addAndGet(islandPlacer.getWritesDroppedBottomOverhang());
|
||||
}
|
||||
|
||||
private static void verifyTerrainBelowObject(IrisObject obj, int wx, int wz, int pickTopY, FloatingIslandSample sample) {
|
||||
if (terrainMismatchWarnings.get() >= TERRAIN_MISMATCH_WARNING_CAP) {
|
||||
return;
|
||||
}
|
||||
if (sample != null
|
||||
&& sample.solidMask != null
|
||||
&& sample.topIdx >= 0
|
||||
&& sample.topIdx < sample.solidMask.length
|
||||
&& sample.solidMask[sample.topIdx]) {
|
||||
return;
|
||||
}
|
||||
if (terrainMismatchWarnings.incrementAndGet() > TERRAIN_MISMATCH_WARNING_CAP) {
|
||||
return;
|
||||
}
|
||||
String objKey = obj == null ? "<null>" : obj.getLoadKey();
|
||||
String sampleTop = sample == null ? "null" : String.valueOf(sample.topY());
|
||||
String sampleBase = sample == null ? "null" : String.valueOf(sample.islandBaseY);
|
||||
String sampleTopIdx = sample == null ? "null" : String.valueOf(sample.topIdx);
|
||||
String sampleMaskLen = sample == null || sample.solidMask == null ? "null" : String.valueOf(sample.solidMask.length);
|
||||
Iris.warn("[FloatingTerrainCheck] object=" + objKey
|
||||
+ " at=(" + wx + "," + (pickTopY + 1) + "," + wz + ")"
|
||||
+ " sample reports non-solid trunk column"
|
||||
+ " sampleTopY=" + sampleTop
|
||||
+ " sampleBaseY=" + sampleBase
|
||||
+ " sampleTopIdx=" + sampleTopIdx
|
||||
+ " sampleMaskLen=" + sampleMaskLen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||
IrisComplex complex = context.getComplex();
|
||||
@@ -184,7 +149,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
}
|
||||
|
||||
java.util.IdentityHashMap<IrisFloatingChildBiomes, KList<Integer>> entryColumns = new java.util.IdentityHashMap<>();
|
||||
IdentityHashMap<IrisFloatingChildBiomes, KList<Integer>> entryColumns = new IdentityHashMap<>();
|
||||
for (int i = 0; i < 256; i++) {
|
||||
FloatingIslandSample s = samples[i];
|
||||
if (s == null || s.entry == null) {
|
||||
@@ -193,7 +158,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
entryColumns.computeIfAbsent(s.entry, e -> new KList<>()).add(i);
|
||||
}
|
||||
|
||||
for (java.util.Map.Entry<IrisFloatingChildBiomes, KList<Integer>> ec : entryColumns.entrySet()) {
|
||||
for (Map.Entry<IrisFloatingChildBiomes, KList<Integer>> ec : entryColumns.entrySet()) {
|
||||
IrisFloatingChildBiomes entry = ec.getKey();
|
||||
KList<Integer> columns = ec.getValue();
|
||||
if (columns.isEmpty()) {
|
||||
@@ -280,7 +245,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
try {
|
||||
obj.place(xx, -1, zz, writer, floatingPlacement, rng, (b, bd) -> {
|
||||
String marker = placementMarker(obj, id);
|
||||
if (marker != null) {
|
||||
if (marker != null && shouldWritePlacementMarker(writer, bd, b.getX(), b.getY(), b.getZ())) {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||
}
|
||||
}, null, data);
|
||||
@@ -367,16 +332,15 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
try {
|
||||
obj.place(wx, yv, wz, islandPlacer, anchored, rng, (b, bd) -> {
|
||||
String marker = placementMarker(obj, id);
|
||||
if (marker != null) {
|
||||
if (marker != null
|
||||
&& islandPlacer.canWriteObjectBlock(b.getX(), b.getY(), b.getZ())
|
||||
&& shouldWritePlacementMarker(islandPlacer, bd, b.getX(), b.getY(), b.getZ())) {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||
}
|
||||
}, null, data);
|
||||
objectsPlaced.incrementAndGet();
|
||||
recordAnchorYHisto(pickTopY);
|
||||
int trunkWx = minX + pickedXf;
|
||||
int trunkWz = minZ + pickedZf;
|
||||
verifyTerrainBelowObject(obj, trunkWx, trunkWz, pickTopY, pickedSample);
|
||||
recordWriteStats(obj, trunkWx, trunkWz, pickTopY, islandPlacer);
|
||||
recordWriteStats(islandPlacer);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
@@ -417,44 +381,56 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
final IrisObject obj = obj0;
|
||||
|
||||
FloatingObjectFootprint fp = FloatingObjectFootprint.compute(obj);
|
||||
int invertedYRotation = rng.i(0, 3) * 90;
|
||||
IrisObjectRotation invertedRotation = IrisObjectRotation.xFlip180WithY(invertedYRotation);
|
||||
|
||||
KList<Integer> pool = interior.isEmpty() ? columns : interior;
|
||||
if (interior.isEmpty()) {
|
||||
objectsInvertedFallbackNoInterior.incrementAndGet();
|
||||
}
|
||||
|
||||
int pickedKey = pool.get(rng.i(0, pool.size() - 1));
|
||||
int pickedXf = pickedKey & 15;
|
||||
int pickedZf = pickedKey >> 4;
|
||||
FloatingIslandSample pickedSample = samples[(pickedZf << 4) | pickedXf];
|
||||
if (pickedSample == null) {
|
||||
objectsInvertedSkippedNoFlat.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
int pickBottomY = pickedSample.bottomY();
|
||||
if (pickBottomY < 0) {
|
||||
objectsInvertedSkippedNoFlat.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isFootprintFlatBottom(fp, pickedXf, pickedZf, pickBottomY, samples, 2)) {
|
||||
if (!isFootprintFlatBottom(fp, pickedXf, pickedZf, pickBottomY, samples, 4)) {
|
||||
objectsInvertedSkippedNoFlat.incrementAndGet();
|
||||
int pickedXf = -1;
|
||||
int pickedZf = -1;
|
||||
int pickBottomY = -1;
|
||||
boolean foundBottomAnchor = false;
|
||||
for (int attempt = 0; attempt < INVERTED_PICK_ATTEMPTS; attempt++) {
|
||||
int pickedKey = pool.get(rng.i(0, pool.size() - 1));
|
||||
int candidateXf = pickedKey & 15;
|
||||
int candidateZf = pickedKey >> 4;
|
||||
FloatingIslandSample candidateSample = samples[(candidateZf << 4) | candidateXf];
|
||||
if (candidateSample == null) {
|
||||
continue;
|
||||
}
|
||||
int candidateBottomY = candidateSample.bottomY();
|
||||
if (candidateBottomY < 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isFootprintFlatBottom(fp, invertedRotation, candidateXf, candidateZf, candidateBottomY, samples, 2)
|
||||
&& !isFootprintFlatBottom(fp, invertedRotation, candidateXf, candidateZf, candidateBottomY, samples, 4)) {
|
||||
continue;
|
||||
}
|
||||
pickedXf = candidateXf;
|
||||
pickedZf = candidateZf;
|
||||
pickBottomY = candidateBottomY;
|
||||
foundBottomAnchor = true;
|
||||
break;
|
||||
}
|
||||
if (!foundBottomAnchor) {
|
||||
objectsInvertedSkippedNoFlat.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
|
||||
int wx = minX + pickedXf - fp.getTallestKxBottom();
|
||||
int wz = minZ + pickedZf - fp.getTallestKzBottom();
|
||||
int wx = invertedBaseX(minX, pickedXf, fp, invertedRotation);
|
||||
int wz = invertedBaseZ(minZ, pickedZf, fp, invertedRotation);
|
||||
|
||||
IrisObjectPlacement inverted = placement.toPlacement(obj.getLoadKey());
|
||||
inverted.setMode(translateStiltModeForFloating(inverted.getMode()));
|
||||
inverted.setTranslate(new IrisObjectTranslate());
|
||||
inverted.setRotation(IrisObjectRotation.xFlip180());
|
||||
inverted.setRotation(invertedRotation);
|
||||
inverted.setForcePlace(true);
|
||||
inverted.setBottom(false);
|
||||
|
||||
int yv = pickBottomY - 1 + fp.getHighestSolidKeyY();
|
||||
int yv = invertedBaseY(pickBottomY, fp, invertedRotation);
|
||||
|
||||
IslandObjectPlacer islandPlacer = new IslandObjectPlacer(writer, samples, minX, minZ, pickBottomY, IslandObjectPlacer.AnchorFace.BOTTOM);
|
||||
int id = rng.i(0, Integer.MAX_VALUE);
|
||||
@@ -462,7 +438,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
try {
|
||||
obj.place(wx, yv, wz, islandPlacer, inverted, rng, (b, bd) -> {
|
||||
String marker = placementMarker(obj, id);
|
||||
if (marker != null) {
|
||||
if (marker != null
|
||||
&& islandPlacer.canWriteObjectBlock(b.getX(), b.getY(), b.getZ())
|
||||
&& shouldWritePlacementMarker(islandPlacer, bd, b.getX(), b.getY(), b.getZ())) {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||
}
|
||||
}, null, data);
|
||||
@@ -474,9 +452,8 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFootprintFlatBottom(FloatingObjectFootprint fp, int pickedXf, int pickedZf, int pickBottomY, FloatingIslandSample[] samples, int tolerance) {
|
||||
int tallestKxBottom = fp.getTallestKxBottom();
|
||||
int tallestKzBottom = fp.getTallestKzBottom();
|
||||
private static boolean isFootprintFlatBottom(FloatingObjectFootprint fp, IrisObjectRotation rotation, int pickedXf, int pickedZf, int pickBottomY, FloatingIslandSample[] samples, int tolerance) {
|
||||
BlockVector anchor = invertedFootprintAnchor(fp, rotation);
|
||||
int checked = 0;
|
||||
boolean touchedChunkEdge = false;
|
||||
long[] cells = fp.footprintXZ();
|
||||
@@ -484,8 +461,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
long encoded = cells[i];
|
||||
int kx = (int) (encoded >> 32);
|
||||
int kz = (int) (encoded & 0xFFFFFFFFL);
|
||||
int colXf = pickedXf + (kx - tallestKxBottom);
|
||||
int colZf = pickedZf + (kz - tallestKzBottom);
|
||||
BlockVector cell = rotation.rotate(new BlockVector(kx, 0, kz), 0, 0, 0);
|
||||
int colXf = pickedXf + cell.getBlockX() - anchor.getBlockX();
|
||||
int colZf = pickedZf + cell.getBlockZ() - anchor.getBlockZ();
|
||||
if (colXf < 0 || colXf >= 16 || colZf < 0 || colZf >= 16) {
|
||||
touchedChunkEdge = true;
|
||||
continue;
|
||||
@@ -506,6 +484,48 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
return touchedChunkEdge;
|
||||
}
|
||||
|
||||
static int invertedBaseX(int minX, int pickedXf, FloatingObjectFootprint fp) {
|
||||
return invertedBaseX(minX, pickedXf, fp, IrisObjectRotation.xFlip180());
|
||||
}
|
||||
|
||||
static int invertedBaseY(int pickBottomY, FloatingObjectFootprint fp) {
|
||||
return invertedBaseY(pickBottomY, fp, IrisObjectRotation.xFlip180());
|
||||
}
|
||||
|
||||
static int invertedBaseZ(int minZ, int pickedZf, FloatingObjectFootprint fp) {
|
||||
return invertedBaseZ(minZ, pickedZf, fp, IrisObjectRotation.xFlip180());
|
||||
}
|
||||
|
||||
static int invertedBaseX(int minX, int pickedXf, FloatingObjectFootprint fp, IrisObjectRotation rotation) {
|
||||
return minX + pickedXf - invertedFootprintAnchor(fp, rotation).getBlockX();
|
||||
}
|
||||
|
||||
static int invertedBaseY(int pickBottomY, FloatingObjectFootprint fp, IrisObjectRotation rotation) {
|
||||
return pickBottomY - 1 - invertedSolidAnchor(fp, rotation).getBlockY();
|
||||
}
|
||||
|
||||
static int invertedBaseZ(int minZ, int pickedZf, FloatingObjectFootprint fp, IrisObjectRotation rotation) {
|
||||
return minZ + pickedZf - invertedFootprintAnchor(fp, rotation).getBlockZ();
|
||||
}
|
||||
|
||||
private static BlockVector invertedFootprintAnchor(FloatingObjectFootprint fp, IrisObjectRotation rotation) {
|
||||
return rotation.rotate(new BlockVector(fp.getTallestKx(), 0, fp.getTallestKz()), 0, 0, 0);
|
||||
}
|
||||
|
||||
private static BlockVector invertedSolidAnchor(FloatingObjectFootprint fp, IrisObjectRotation rotation) {
|
||||
return rotation.rotate(new BlockVector(fp.getTallestKx(), fp.getLowestSolidKeyY(), fp.getTallestKz()), 0, 0, 0);
|
||||
}
|
||||
|
||||
private static boolean shouldWritePlacementMarker(IObjectPlacer placer, BlockData data, int x, int y, int z) {
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
BlockData existing = placer.get(x, y, z);
|
||||
boolean wouldReplace = existing != null && B.isSolid(existing) && B.isVineBlock(data);
|
||||
boolean placesBlock = !data.getMaterial().equals(Material.AIR) && !data.getMaterial().equals(Material.CAVE_AIR) && !wouldReplace;
|
||||
return data instanceof IrisCustomData || placesBlock;
|
||||
}
|
||||
|
||||
private static boolean isFootprintFlat(FloatingObjectFootprint fp, int pickedXf, int pickedZf, int pickTopY, FloatingIslandSample[] samples, int tolerance) {
|
||||
int tallestKx = fp.getTallestKx();
|
||||
int tallestKz = fp.getTallestKz();
|
||||
@@ -556,10 +576,18 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
if (xf <= 0 || xf >= 15 || zf <= 0 || zf >= 15) {
|
||||
continue;
|
||||
}
|
||||
if (samples[(zf << 4) | (xf + 1)] == null) continue;
|
||||
if (samples[(zf << 4) | (xf - 1)] == null) continue;
|
||||
if (samples[((zf + 1) << 4) | xf] == null) continue;
|
||||
if (samples[((zf - 1) << 4) | xf] == null) continue;
|
||||
if (samples[(zf << 4) | (xf + 1)] == null) {
|
||||
continue;
|
||||
}
|
||||
if (samples[(zf << 4) | (xf - 1)] == null) {
|
||||
continue;
|
||||
}
|
||||
if (samples[((zf + 1) << 4) | xf] == null) {
|
||||
continue;
|
||||
}
|
||||
if (samples[((zf - 1) << 4) | xf] == null) {
|
||||
continue;
|
||||
}
|
||||
interior.add(key);
|
||||
}
|
||||
return interior;
|
||||
@@ -592,7 +620,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
@Override
|
||||
protected int computeRadius() {
|
||||
int maxObjectExtent = 0;
|
||||
java.util.Set<String> objectKeys = new java.util.HashSet<>();
|
||||
Set<String> objectKeys = new HashSet<>();
|
||||
try {
|
||||
IrisData data = getData();
|
||||
for (IrisBiome biome : getDimension().getAllBiomes(this::getData)) {
|
||||
@@ -617,11 +645,15 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
for (String key : objectKeys) {
|
||||
try {
|
||||
java.io.File f = data.getObjectLoader().findFile(key);
|
||||
if (f == null) continue;
|
||||
org.bukkit.util.BlockVector sz = IrisObject.sampleSize(f);
|
||||
File f = data.getObjectLoader().findFile(key);
|
||||
if (f == null) {
|
||||
continue;
|
||||
}
|
||||
BlockVector sz = IrisObject.sampleSize(f);
|
||||
int extent = Math.max(sz.getBlockX(), sz.getBlockZ());
|
||||
if (extent > maxObjectExtent) maxObjectExtent = extent;
|
||||
if (extent > maxObjectExtent) {
|
||||
maxObjectExtent = extent;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
@@ -630,10 +662,14 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
return Math.max(16, maxObjectExtent);
|
||||
}
|
||||
|
||||
private static void collectPlacementKeys(KList<IrisObjectPlacement> placements, java.util.Set<String> out) {
|
||||
if (placements == null) return;
|
||||
private static void collectPlacementKeys(KList<IrisObjectPlacement> placements, Set<String> out) {
|
||||
if (placements == null) {
|
||||
return;
|
||||
}
|
||||
for (IrisObjectPlacement p : placements) {
|
||||
if (p == null || p.getPlace() == null) continue;
|
||||
if (p == null || p.getPlace() == null) {
|
||||
continue;
|
||||
}
|
||||
out.addAll(p.getPlace());
|
||||
}
|
||||
}
|
||||
|
||||
+114
-118
@@ -18,25 +18,29 @@
|
||||
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.decorator.FloatingDecorator;
|
||||
import art.arcane.iris.engine.decorator.IrisSeaSurfaceDecorator;
|
||||
import static art.arcane.iris.engine.mantle.EngineMantle.AIR;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedModifier;
|
||||
import art.arcane.iris.engine.framework.EngineDecorator;
|
||||
import art.arcane.iris.engine.mantle.components.MantleFloatingObjectComponent;
|
||||
import art.arcane.iris.engine.object.FloatingBottomPaletteMode;
|
||||
import art.arcane.iris.engine.object.FloatingIslandSample;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomePaletteLayer;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
||||
import art.arcane.iris.engine.object.IrisSlopeClip;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.MatterBiomeInject;
|
||||
@@ -45,110 +49,125 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import static art.arcane.iris.engine.mantle.EngineMantle.AIR;
|
||||
|
||||
public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<BlockData> {
|
||||
public static final long FLOATING_BASE_SEED_SALT = 0x5EED_F107_00F1B10CL;
|
||||
private static final AtomicLong columnsChecked = new AtomicLong();
|
||||
private static final AtomicLong samplesAccepted = new AtomicLong();
|
||||
private static final AtomicLong decorateInvocations = new AtomicLong();
|
||||
private static final AtomicLong decorateSkippedNotAir = new AtomicLong();
|
||||
private static final AtomicLong decorateSkippedNoInherit = new AtomicLong();
|
||||
private static final AtomicLong decoratePhaseColumns = new AtomicLong();
|
||||
private static final AtomicLong decoratePlaced = new AtomicLong();
|
||||
private static final AtomicLong decorateNoChange = new AtomicLong();
|
||||
private static final AtomicLong decorateFloorNull = new AtomicLong();
|
||||
private static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> floorMatHisto = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
private static final AtomicLong decCandidatesNull = new AtomicLong();
|
||||
private static final AtomicLong lastReportMs = new AtomicLong(0L);
|
||||
private static final AtomicLong reportCycle = new AtomicLong(0L);
|
||||
private static final Runnable INC_DEC_CANDIDATES_NULL = () -> decCandidatesNull.incrementAndGet();
|
||||
private static final Runnable NOOP_DECORATION_MISS = () -> {
|
||||
};
|
||||
private final RNG rng;
|
||||
private final EngineDecorator seaSurfaceDecorator;
|
||||
|
||||
public static void reportFloatingStats() {
|
||||
StringBuilder topFloors = new StringBuilder();
|
||||
floorMatHisto.entrySet().stream()
|
||||
.sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
|
||||
.limit(5)
|
||||
.forEach(e -> topFloors.append(' ').append(e.getKey()).append('=').append(e.getValue().get()));
|
||||
|
||||
StringBuilder topAnchorY = new StringBuilder();
|
||||
MantleFloatingObjectComponent.anchorYHisto.entrySet().stream()
|
||||
.sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
|
||||
.limit(5)
|
||||
.forEach(e -> topAnchorY.append(' ').append(e.getKey()).append('=').append(e.getValue().get()));
|
||||
|
||||
art.arcane.iris.Iris.info("[floating-debug]"
|
||||
+ " columns=" + columnsChecked.get()
|
||||
+ " samples=" + samplesAccepted.get()
|
||||
+ " decInvoke=" + decorateInvocations.get()
|
||||
+ " decPlaced=" + decoratePlaced.get()
|
||||
+ " decNoChange=" + decorateNoChange.get()
|
||||
+ " decFloorNull=" + decorateFloorNull.get()
|
||||
+ " decCandidatesNull=" + decCandidatesNull.get()
|
||||
+ " decSkipNonAir=" + decorateSkippedNotAir.get()
|
||||
+ " decSkipNoInherit=" + decorateSkippedNoInherit.get()
|
||||
+ " decPhaseCols=" + decoratePhaseColumns.get()
|
||||
+ " objAttempt=" + MantleFloatingObjectComponent.objectsAttempted.get()
|
||||
+ " objPlaced=" + MantleFloatingObjectComponent.objectsPlaced.get()
|
||||
+ " objNoFlat=" + MantleFloatingObjectComponent.objectsSkippedNoFlat.get()
|
||||
+ " objNoInterior=" + MantleFloatingObjectComponent.objectsSkippedNoInterior.get()
|
||||
+ " objRelax=" + MantleFloatingObjectComponent.objectsRelaxed.get()
|
||||
+ " objShrinkDrop=" + MantleFloatingObjectComponent.objectsSkippedShrink.get()
|
||||
+ " objNullObj=" + MantleFloatingObjectComponent.objectsSkippedNullObj.get()
|
||||
+ " writeAttempt=" + MantleFloatingObjectComponent.writesAttemptedTotal.get()
|
||||
+ " writeDropBelow=" + MantleFloatingObjectComponent.writesDroppedBelowTotal.get()
|
||||
+ " writeDropOverhang=" + MantleFloatingObjectComponent.writesDroppedOverhangTotal.get()
|
||||
+ " terrainMismatch=" + MantleFloatingObjectComponent.terrainMismatchWarnings.get()
|
||||
+ " objInvAttempt=" + MantleFloatingObjectComponent.objectsInvertedAttempted.get()
|
||||
+ " objInvPlaced=" + MantleFloatingObjectComponent.objectsInvertedPlaced.get()
|
||||
+ " objInvNoFlat=" + MantleFloatingObjectComponent.objectsInvertedSkippedNoFlat.get()
|
||||
+ " objInvFallbackNoInterior=" + MantleFloatingObjectComponent.objectsInvertedFallbackNoInterior.get()
|
||||
+ " writesAboveBottom=" + MantleFloatingObjectComponent.writesDroppedAboveBottomTotal.get()
|
||||
+ " writesBottomOverhang=" + MantleFloatingObjectComponent.writesDroppedBottomOverhangTotal.get()
|
||||
+ " anchorY:" + (topAnchorY.length() == 0 ? " <none>" : topAnchorY.toString())
|
||||
+ " topFloors:" + (topFloors.length() == 0 ? " <none>" : topFloors.toString()));
|
||||
private static KList<BlockData> generateBottomPaletteLayers(IrisFloatingChildBiomes entry, IrisDimension dimension, double wx, double wz, RNG random, int paletteDepth, IrisData data, IrisComplex complex) {
|
||||
if (entry == null || entry.getBottomPaletteMode() != FloatingBottomPaletteMode.CUSTOM || entry.getBottomPalette() == null || entry.getBottomPalette().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return generatePaletteLayers(dimension, entry.getBottomPalette(), wx, wz, random.nextParallelRNG(0xB0770B), paletteDepth, data, complex);
|
||||
}
|
||||
|
||||
private static void maybeReport() {
|
||||
long now = System.currentTimeMillis();
|
||||
long last = lastReportMs.get();
|
||||
if (now - last >= 10000L && lastReportMs.compareAndSet(last, now)) {
|
||||
reportFloatingStats();
|
||||
if (reportCycle.incrementAndGet() >= 30) {
|
||||
reportCycle.set(0);
|
||||
resetAllCounters();
|
||||
private static KList<BlockData> generatePaletteLayers(IrisDimension dimension, KList<IrisBiomePaletteLayer> layers, double wx, double wz, RNG random, int maxDepth, IrisData data, IrisComplex complex) {
|
||||
KList<BlockData> generated = new KList<>();
|
||||
if (layers == null || layers.isEmpty() || maxDepth <= 0) {
|
||||
return generated;
|
||||
}
|
||||
|
||||
int generatorSeed = 7235;
|
||||
for (int i = 0; i < layers.size(); i++) {
|
||||
IrisBiomePaletteLayer layer = layers.get(i);
|
||||
CNG heightGenerator = layer.getHeightGenerator(random.nextParallelRNG((generatorSeed++) * generatorSeed * generatorSeed * generatorSeed), data);
|
||||
if (heightGenerator == null) {
|
||||
continue;
|
||||
}
|
||||
double layerDepth = heightGenerator.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / layer.getZoom(), wz / layer.getZoom());
|
||||
IrisSlopeClip slopeClip = layer.getSlopeCondition();
|
||||
|
||||
if (slopeClip != null && !slopeClip.isDefault() && complex != null && !slopeClip.isValid(complex.getSlopeStream().get(wx, wz))) {
|
||||
layerDepth = 0;
|
||||
}
|
||||
|
||||
if (layerDepth <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < layerDepth; j++) {
|
||||
if (generated.size() >= maxDepth) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
generated.add(layer.get(random.nextParallelRNG(i + j), (wx + j) / layer.getZoom(), j, (wz - j) / layer.getZoom(), data));
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (generated.size() >= maxDepth) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dimension != null && dimension.isExplodeBiomePalettes()) {
|
||||
BlockData barrier = B.get("minecraft:barrier");
|
||||
for (int j = 0; j < dimension.getExplodeBiomePaletteSize(); j++) {
|
||||
generated.add(barrier);
|
||||
|
||||
if (generated.size() >= maxDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return generated;
|
||||
}
|
||||
|
||||
private static void resetAllCounters() {
|
||||
columnsChecked.set(0);
|
||||
samplesAccepted.set(0);
|
||||
decorateInvocations.set(0);
|
||||
decorateSkippedNotAir.set(0);
|
||||
decorateSkippedNoInherit.set(0);
|
||||
decoratePhaseColumns.set(0);
|
||||
decoratePlaced.set(0);
|
||||
decorateNoChange.set(0);
|
||||
decorateFloorNull.set(0);
|
||||
floorMatHisto.clear();
|
||||
decCandidatesNull.set(0);
|
||||
MantleFloatingObjectComponent.resetObjectCounters();
|
||||
private static boolean usesBottomPalette(IrisFloatingChildBiomes entry) {
|
||||
FloatingBottomPaletteMode mode = entry == null || entry.getBottomPaletteMode() == null ? FloatingBottomPaletteMode.DEPTH : entry.getBottomPaletteMode();
|
||||
return mode != FloatingBottomPaletteMode.DEPTH;
|
||||
}
|
||||
|
||||
private static void recordFloorMat(String matKey) {
|
||||
if (floorMatHisto.size() < 32) {
|
||||
floorMatHisto.computeIfAbsent(matKey, k -> new AtomicLong()).incrementAndGet();
|
||||
} else {
|
||||
AtomicLong existing = floorMatHisto.get(matKey);
|
||||
if (existing != null) {
|
||||
existing.incrementAndGet();
|
||||
} else {
|
||||
floorMatHisto.computeIfAbsent("other", k -> new AtomicLong()).incrementAndGet();
|
||||
}
|
||||
private static int[] bottomDepths(FloatingIslandSample sample, int chunkHeight) {
|
||||
int[] bottomDepths = new int[sample.solidMask.length];
|
||||
for (int i = 0; i < bottomDepths.length; i++) {
|
||||
bottomDepths[i] = -1;
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
int max = Math.min(sample.topIdx, sample.solidMask.length - 1);
|
||||
for (int k = 0; k <= max; k++) {
|
||||
if (!sample.solidMask[k]) {
|
||||
continue;
|
||||
}
|
||||
int y = sample.islandBaseY + k;
|
||||
if (y < 0 || y >= chunkHeight) {
|
||||
continue;
|
||||
}
|
||||
bottomDepths[k] = depth++;
|
||||
}
|
||||
|
||||
return bottomDepths;
|
||||
}
|
||||
|
||||
private static BlockData selectPaletteBlock(IrisFloatingChildBiomes entry, KList<BlockData> topBlocks, KList<BlockData> bottomBlocks, int topDepth, int bottomDepth, BlockData fallbackSolid) {
|
||||
FloatingBottomPaletteMode mode = entry == null || entry.getBottomPaletteMode() == null ? FloatingBottomPaletteMode.DEPTH : entry.getBottomPaletteMode();
|
||||
if (mode == FloatingBottomPaletteMode.MIRROR_TOP) {
|
||||
return paletteBlock(topBlocks, Math.min(topDepth, bottomDepth), fallbackSolid);
|
||||
}
|
||||
if (mode == FloatingBottomPaletteMode.CUSTOM && bottomDepth < topDepth) {
|
||||
if (bottomBlocks != null && !bottomBlocks.isEmpty()) {
|
||||
return paletteBlock(bottomBlocks, bottomDepth, fallbackSolid);
|
||||
}
|
||||
return paletteBlock(topBlocks, bottomDepth, fallbackSolid);
|
||||
}
|
||||
return paletteBlock(topBlocks, topDepth, fallbackSolid);
|
||||
}
|
||||
|
||||
private static BlockData paletteBlock(KList<BlockData> blocks, int depth, BlockData fallbackSolid) {
|
||||
if (blocks == null || blocks.isEmpty()) {
|
||||
return fallbackSolid;
|
||||
}
|
||||
BlockData block = blocks.hasIndex(depth) ? blocks.get(depth) : blocks.getLast();
|
||||
return block == null ? fallbackSolid : block;
|
||||
}
|
||||
|
||||
public IrisFloatingChildBiomeModifier(Engine engine) {
|
||||
@@ -174,13 +193,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
columnsChecked.incrementAndGet();
|
||||
|
||||
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, getEngine());
|
||||
if (sample == null) {
|
||||
continue;
|
||||
}
|
||||
samplesAccepted.incrementAndGet();
|
||||
|
||||
IrisFloatingChildBiomes entry = sample.entry;
|
||||
IrisBiome target = entry.getRealBiome(parent, data);
|
||||
@@ -191,8 +207,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
if (blocks == null || blocks.isEmpty()) {
|
||||
blocks = parent.generateLayers(dimension, wx, wz, layerRng, paletteDepth, paletteDepth, data, complex);
|
||||
}
|
||||
KList<BlockData> bottomBlocks = generateBottomPaletteLayers(entry, dimension, wx, wz, layerRng, paletteDepth, data, complex);
|
||||
BlockData fallbackSolid = B.get("minecraft:stone");
|
||||
|
||||
int[] bottomDepths = usesBottomPalette(entry) ? bottomDepths(sample, chunkHeight) : null;
|
||||
int depth = 0;
|
||||
for (int k = sample.topIdx; k >= 0; k--) {
|
||||
if (!sample.solidMask[k]) {
|
||||
@@ -202,13 +220,8 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
if (y < 0 || y >= chunkHeight) {
|
||||
continue;
|
||||
}
|
||||
BlockData block = null;
|
||||
if (blocks != null && !blocks.isEmpty()) {
|
||||
block = blocks.hasIndex(depth) ? blocks.get(depth) : blocks.getLast();
|
||||
}
|
||||
if (block == null) {
|
||||
block = fallbackSolid;
|
||||
}
|
||||
int bottomDepth = bottomDepths == null || bottomDepths[k] < 0 ? depth : bottomDepths[k];
|
||||
BlockData block = selectPaletteBlock(entry, blocks, bottomBlocks, depth, bottomDepth, fallbackSolid);
|
||||
if (block != null) {
|
||||
output.set(xf, y, zf, block);
|
||||
}
|
||||
@@ -270,12 +283,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
if (sample == null) {
|
||||
continue;
|
||||
}
|
||||
decoratePhaseColumns.incrementAndGet();
|
||||
IrisFloatingChildBiomes entry = sample.entry;
|
||||
IrisBiome target = entry.getRealBiome(parent, data);
|
||||
|
||||
if (!entry.isInheritDecorators() || target == null) {
|
||||
decorateSkippedNoInherit.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -284,26 +295,12 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
if (topY + 1 < chunkHeight) {
|
||||
BlockData above = output.get(xf, topY + 1, zf);
|
||||
if (above == null || B.isAir(above)) {
|
||||
decorateInvocations.incrementAndGet();
|
||||
BlockData floor = topY >= 0 && topY < chunkHeight ? output.get(xf, topY, zf) : null;
|
||||
if (floor == null) {
|
||||
decorateFloorNull.incrementAndGet();
|
||||
} else {
|
||||
recordFloorMat(floor.getMaterial().getKey().getKey());
|
||||
}
|
||||
try {
|
||||
RNG colRng = rng.nextParallelRNG((int) FloatingIslandSample.columnSeed(baseSeed, wx, wz));
|
||||
int placed = FloatingDecorator.decorateColumn(getEngine(), target, IrisDecorationPart.NONE, xf, zf, wx, wz, topY, max, output, colRng, INC_DEC_CANDIDATES_NULL);
|
||||
if (placed > 0) {
|
||||
decoratePlaced.addAndGet(placed);
|
||||
} else {
|
||||
decorateNoChange.incrementAndGet();
|
||||
}
|
||||
FloatingDecorator.decorateColumn(getEngine(), target, IrisDecorationPart.NONE, xf, zf, wx, wz, topY, max, output, colRng, NOOP_DECORATION_MISS);
|
||||
} catch (Throwable e) {
|
||||
art.arcane.iris.Iris.reportError(e);
|
||||
}
|
||||
} else {
|
||||
decorateSkippedNotAir.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +337,6 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
}
|
||||
}
|
||||
}
|
||||
maybeReport();
|
||||
}
|
||||
|
||||
private void writeIslandSkyBiome(IrisBiome target, int wx, int wz, FloatingIslandSample sample, int chunkHeight) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Controls how floating-child island underside blocks choose their palette.")
|
||||
public enum FloatingBottomPaletteMode {
|
||||
@Desc("Use the normal top-down biome layer depth for the whole island column.")
|
||||
DEPTH,
|
||||
|
||||
@Desc("Use the target biome's top palette from both the island top and underside, meeting in the middle.")
|
||||
MIRROR_TOP,
|
||||
|
||||
@Desc("Use bottomPalette near the underside and the target biome's normal palette near the top.")
|
||||
CUSTOM
|
||||
}
|
||||
@@ -38,6 +38,7 @@ public final class FloatingIslandSample {
|
||||
public static final int REJECT_COUNT = 7;
|
||||
public static final int REJECT_CLUSTER = REJECT_NO_SEED;
|
||||
|
||||
private static final double EDGE_ROUNDING_BAND = 0.28;
|
||||
private static final ThreadLocal<int[]> LAST_REJECT = ThreadLocal.withInitial(() -> new int[1]);
|
||||
private static final ThreadLocal<double[]> LAST_DENSITY = ThreadLocal.withInitial(() -> new double[2]);
|
||||
private static final ThreadLocal<HashMap<Long, FloatingIslandSample>> CHUNK_MEMO = ThreadLocal.withInitial(HashMap::new);
|
||||
@@ -169,6 +170,9 @@ public final class FloatingIslandSample {
|
||||
if (signed <= signedCut) {
|
||||
return reject(REJECT_NO_SEED);
|
||||
}
|
||||
if (!hasFootprintNeighborSupport(footprintCng, wx, wz, signedCut)) {
|
||||
return reject(REJECT_NO_SEED);
|
||||
}
|
||||
|
||||
CNG altitudeCng = entry.getAltitudeCng(baseSeed, data);
|
||||
if (altitudeCng == null) {
|
||||
@@ -182,14 +186,11 @@ public final class FloatingIslandSample {
|
||||
int maxAlt = Math.max(minAlt, entry.getMaxHeightAboveSurface() - worldMin);
|
||||
int baseY = minAlt + (int) Math.round(altClamped * (maxAlt - minAlt));
|
||||
|
||||
double edgeFade = edgeFade(signed, signedCut);
|
||||
IrisBiome target = entry.getRealBiome(parent, data);
|
||||
int topH = computeTopHeight(entry, target, engine, baseSeed, wx, wz, data);
|
||||
int topH = roundedEdgeHeight(computeTopHeight(entry, target, engine, baseSeed, wx, wz, data), edgeFade);
|
||||
int topY = baseY + topH;
|
||||
|
||||
double edge = (signed - signedCut) / 0.15;
|
||||
double edgeClamped = Math.max(0, Math.min(1, edge));
|
||||
double edgeFade = edgeClamped * edgeClamped * (3.0 - 2.0 * edgeClamped);
|
||||
|
||||
CNG bottomCng = entry.getBottomCng(baseSeed, data);
|
||||
if (bottomCng == null) {
|
||||
warnNullCng("bottomStyle", parent);
|
||||
@@ -200,7 +201,7 @@ public final class FloatingIslandSample {
|
||||
double bottomShaped = Math.pow(bottomClamped, Math.max(0.1, entry.getBottomExponent()));
|
||||
int minDepth = Math.max(0, entry.getBottomDepthMin());
|
||||
int maxDepth = Math.max(minDepth, entry.getBottomDepthMax());
|
||||
int depth = minDepth + (int) Math.round(bottomShaped * (maxDepth - minDepth) * edgeFade);
|
||||
int depth = roundedEdgeDepth(minDepth, maxDepth, bottomShaped, edgeFade);
|
||||
int botY = baseY - depth;
|
||||
|
||||
Integer minAbsoluteY = entry.getMinAbsoluteY();
|
||||
@@ -242,8 +243,6 @@ public final class FloatingIslandSample {
|
||||
double carveThreshold = entry.getCarveThreshold();
|
||||
boolean useWarp = wallWarp != null && warpAmp > 0;
|
||||
boolean useCarve = carve != null && carveThreshold < 1.0;
|
||||
int solidCount = 0;
|
||||
int highestSolidIdx = -1;
|
||||
|
||||
for (int k = 0; k < thickness; k++) {
|
||||
int wy = botY + k;
|
||||
@@ -262,24 +261,14 @@ public final class FloatingIslandSample {
|
||||
if (layerSigned <= signedCut) {
|
||||
continue;
|
||||
}
|
||||
if (useCarve) {
|
||||
double cn = carve.noise(wx, wy, wz);
|
||||
double cnClamped = Math.max(0, Math.min(1, cn));
|
||||
if (cnClamped > carveThreshold) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
solidMask[k] = true;
|
||||
solidCount++;
|
||||
if (k > highestSolidIdx) {
|
||||
highestSolidIdx = k;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useCarve) {
|
||||
solidCount = solidifyUncarvedInterior(solidMask);
|
||||
highestSolidIdx = highestSolidIndex(solidMask);
|
||||
int solidCount = solidifyUncarvedInterior(solidMask);
|
||||
if (useCarve) {
|
||||
solidCount = carveSolidInterior(solidMask, botY, wx, wz, carve, carveThreshold);
|
||||
}
|
||||
int highestSolidIdx = highestSolidIndex(solidMask);
|
||||
|
||||
if (solidCount == 0 || highestSolidIdx < 0) {
|
||||
return reject(REJECT_NO_SOLID);
|
||||
@@ -291,6 +280,80 @@ public final class FloatingIslandSample {
|
||||
return new FloatingIslandSample(entry, botY, thickness, topIdx, solidCount, solidMask);
|
||||
}
|
||||
|
||||
static double edgeFade(double signed, double signedCut) {
|
||||
double edge = (signed - signedCut) / EDGE_ROUNDING_BAND;
|
||||
double edgeClamped = Math.max(0, Math.min(1, edge));
|
||||
return edgeClamped * edgeClamped * (3.0 - 2.0 * edgeClamped);
|
||||
}
|
||||
|
||||
static int roundedEdgeHeight(int topHeight, double edgeFade) {
|
||||
return Math.max(0, (int) Math.round(Math.max(0, topHeight) * Math.max(0, Math.min(1, edgeFade))));
|
||||
}
|
||||
|
||||
static int roundedEdgeDepth(int minDepth, int maxDepth, double bottomShaped, double edgeFade) {
|
||||
int min = Math.max(0, minDepth);
|
||||
int max = Math.max(min, maxDepth);
|
||||
double shaped = Math.max(0, Math.min(1, bottomShaped));
|
||||
double fade = Math.max(0, Math.min(1, edgeFade));
|
||||
double fullDepth = min + shaped * (max - min);
|
||||
return (int) Math.round(fullDepth * fade);
|
||||
}
|
||||
|
||||
static int carveSolidInterior(boolean[] solidMask, int botY, int wx, int wz, CNG carve, double carveThreshold) {
|
||||
int firstSolid = -1;
|
||||
int lastSolid = -1;
|
||||
for (int i = 0; i < solidMask.length; i++) {
|
||||
if (!solidMask[i]) {
|
||||
continue;
|
||||
}
|
||||
if (firstSolid < 0) {
|
||||
firstSolid = i;
|
||||
}
|
||||
lastSolid = i;
|
||||
}
|
||||
if (firstSolid < 0) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (int i = firstSolid; i <= lastSolid; i++) {
|
||||
if (i != firstSolid && i != lastSolid) {
|
||||
double carveNoise = carve.noise(wx, botY + i, wz);
|
||||
double carveClamped = Math.max(0, Math.min(1, carveNoise));
|
||||
if (carveClamped > carveThreshold) {
|
||||
solidMask[i] = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (solidMask[i]) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static boolean hasFootprintNeighborSupport(CNG footprintCng, int wx, int wz, double signedCut) {
|
||||
int cardinal = 0;
|
||||
int diagonal = 0;
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
if (dx == 0 && dz == 0) {
|
||||
continue;
|
||||
}
|
||||
double footprintValue = footprintCng.noise(wx + dx, wz + dz);
|
||||
double signed = (Math.max(0, Math.min(1, footprintValue)) * 2.0) - 1.0;
|
||||
if (signed <= signedCut) {
|
||||
continue;
|
||||
}
|
||||
if (Math.abs(dx) + Math.abs(dz) == 1) {
|
||||
cardinal++;
|
||||
} else {
|
||||
diagonal++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cardinal > 0 || diagonal >= 2;
|
||||
}
|
||||
|
||||
static int solidifyUncarvedInterior(boolean[] solidMask) {
|
||||
int firstSolid = -1;
|
||||
int lastSolid = -1;
|
||||
|
||||
@@ -18,21 +18,16 @@
|
||||
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.util.BlockVector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class FloatingObjectFootprint {
|
||||
private static final ConcurrentHashMap<String, FloatingObjectFootprint> CACHE = new ConcurrentHashMap<>();
|
||||
private static final boolean DIAGNOSTIC_LOG = Boolean.parseBoolean(System.getProperty("iris.floating.footprintLog", "true"));
|
||||
|
||||
private final int lowestSolidKeyY;
|
||||
private final int highestSolidKeyY;
|
||||
@@ -111,98 +106,9 @@ public class FloatingObjectFootprint {
|
||||
int tallestKz = columnStats.isEmpty() ? 0 : (int) (tallestPacked & 0xFFFFFFFFL);
|
||||
int tallestKxBottom = columnStats.isEmpty() ? 0 : globalHighestKx[0];
|
||||
int tallestKzBottom = columnStats.isEmpty() ? 0 : globalHighestKz[0];
|
||||
if (DIAGNOSTIC_LOG) {
|
||||
logFootprintDiagnostic(cacheKey, obj, cx, cy, cz, lowestSolidKeyY, tallestKx, tallestKz, columnStats);
|
||||
}
|
||||
return new FloatingObjectFootprint(lowestSolidKeyY, highestSolidKeyY, cx, cy, cz, tallestKx, tallestKz, tallestKxBottom, tallestKzBottom, footprintArray);
|
||||
}
|
||||
|
||||
private static void logFootprintDiagnostic(String cacheKey, IrisObject obj, int cx, int cy, int cz, int anchorY, int tallestKx, int tallestKz, Map<Long, int[]> columnStats) {
|
||||
if (columnStats.isEmpty()) {
|
||||
Iris.info("[FloatingFootprint] key=" + cacheKey + " center=(" + cx + "," + cy + "," + cz + ") anchor=" + anchorY + " columns=0 (EMPTY)");
|
||||
return;
|
||||
}
|
||||
|
||||
int tallestCount = 0;
|
||||
int tallestLow = Integer.MAX_VALUE;
|
||||
int floorLow = Integer.MAX_VALUE;
|
||||
int ceilingLow = Integer.MIN_VALUE;
|
||||
int minKx = Integer.MAX_VALUE, maxKx = Integer.MIN_VALUE;
|
||||
int minKz = Integer.MAX_VALUE, maxKz = Integer.MIN_VALUE;
|
||||
TreeMap<Integer, Integer> lowYHisto = new TreeMap<>();
|
||||
for (Map.Entry<Long, int[]> e : columnStats.entrySet()) {
|
||||
long packed = e.getKey();
|
||||
int kx = (int) (packed >> 32);
|
||||
int kz = (int) (packed & 0xFFFFFFFFL);
|
||||
if (kx < minKx) minKx = kx;
|
||||
if (kx > maxKx) maxKx = kx;
|
||||
if (kz < minKz) minKz = kz;
|
||||
if (kz > maxKz) maxKz = kz;
|
||||
int[] s = e.getValue();
|
||||
int count = s[1];
|
||||
int low = s[0];
|
||||
if (count > tallestCount || (count == tallestCount && low < tallestLow)) {
|
||||
tallestCount = count;
|
||||
tallestLow = low;
|
||||
}
|
||||
if (low < floorLow) floorLow = low;
|
||||
if (low > ceilingLow) ceilingLow = low;
|
||||
lowYHisto.merge(low, 1, Integer::sum);
|
||||
}
|
||||
|
||||
int straysBelowAnchor = 0;
|
||||
for (int[] s : columnStats.values()) {
|
||||
if (s[0] < anchorY) straysBelowAnchor++;
|
||||
}
|
||||
|
||||
List<Map.Entry<Long, int[]>> sorted = new ArrayList<>(columnStats.entrySet());
|
||||
sorted.sort((a, b) -> {
|
||||
int cmp = Integer.compare(b.getValue()[1], a.getValue()[1]);
|
||||
if (cmp != 0) return cmp;
|
||||
return Integer.compare(a.getValue()[0], b.getValue()[0]);
|
||||
});
|
||||
StringBuilder topN = new StringBuilder();
|
||||
int showN = Math.min(4, sorted.size());
|
||||
for (int i = 0; i < showN; i++) {
|
||||
Map.Entry<Long, int[]> e = sorted.get(i);
|
||||
int kx = (int) (e.getKey() >> 32);
|
||||
int kz = (int) (e.getKey() & 0xFFFFFFFFL);
|
||||
int[] s = e.getValue();
|
||||
if (i > 0) topN.append(",");
|
||||
topN.append("(").append(kx).append(",").append(kz).append(")c=").append(s[1]).append(":y=").append(s[0]);
|
||||
}
|
||||
|
||||
StringBuilder histo = new StringBuilder();
|
||||
int histoEntries = 0;
|
||||
for (Map.Entry<Integer, Integer> e : lowYHisto.entrySet()) {
|
||||
if (histoEntries++ > 0) histo.append(",");
|
||||
histo.append(e.getKey()).append("=").append(e.getValue());
|
||||
if (histoEntries >= 6) {
|
||||
if (lowYHisto.size() > 6) histo.append(",...+").append(lowYHisto.size() - 6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String keyStyle = (minKx >= 0 && minKz >= 0) ? "RAW" : "SIGNED";
|
||||
|
||||
Iris.info("[FloatingFootprint] key=" + cacheKey
|
||||
+ " dims=" + obj.getW() + "x" + obj.getH() + "x" + obj.getD()
|
||||
+ " center=(" + cx + "," + cy + "," + cz + ")"
|
||||
+ " keyStyle=" + keyStyle
|
||||
+ " kxRange=[" + minKx + "," + maxKx + "]"
|
||||
+ " kzRange=[" + minKz + "," + maxKz + "]"
|
||||
+ " cols=" + columnStats.size()
|
||||
+ " anchor=" + anchorY
|
||||
+ " relAnchor=" + (anchorY - cy)
|
||||
+ " tallestKxKz=(" + tallestKx + "," + tallestKz + ")"
|
||||
+ " floorLow=" + floorLow
|
||||
+ " ceilingLow=" + ceilingLow
|
||||
+ " tallest=count" + tallestCount + ":y" + tallestLow
|
||||
+ " straysBelow=" + straysBelowAnchor
|
||||
+ " topCols=[" + topN + "]"
|
||||
+ " lowYHisto={" + histo + "}");
|
||||
}
|
||||
|
||||
private static long resolveTallestColumn(Map<Long, int[]> columnStats) {
|
||||
long bestPacked = 0L;
|
||||
int tallestCount = 0;
|
||||
|
||||
@@ -162,6 +162,13 @@ public class IrisFloatingChildBiomes implements IRare {
|
||||
@Desc("Power curve applied to the bottom noise before mapping to depth. >1 = most columns shallow with occasional deeper spikes (sparse roots). <1 = most columns deep with occasional shallow spots (dense curtains). 1.0 = linear.")
|
||||
private double bottomExponent = 1.0;
|
||||
|
||||
@Desc("Controls the material palette near the island underside. DEPTH keeps the old top-down depth behavior. MIRROR_TOP uses the target biome's shallow/top palette from the underside upward. CUSTOM uses bottomPalette near the underside while keeping the target biome palette near the top.")
|
||||
private FloatingBottomPaletteMode bottomPaletteMode = FloatingBottomPaletteMode.DEPTH;
|
||||
|
||||
@ArrayType(min = 1, type = IrisBiomePaletteLayer.class)
|
||||
@Desc("Custom palette layers used near the underside when bottomPaletteMode=CUSTOM. The layer format is the same as normal biome layers.")
|
||||
private KList<IrisBiomePaletteLayer> bottomPalette = new KList<>();
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(512)
|
||||
@Desc("Hard cap on the total Y-extent (top minus bottom) of a single island column. Safety limit.")
|
||||
|
||||
@@ -75,6 +75,22 @@ public class IrisObjectRotation {
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static IrisObjectRotation xFlip180RandomY() {
|
||||
IrisObjectRotation rt = xFlip180();
|
||||
rt.setYAxis(new IrisAxisRotationClamp(true, false, 0, 0, 90));
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static IrisObjectRotation xFlip180WithY(double y) {
|
||||
IrisObjectRotation rt = xFlip180();
|
||||
IrisAxisRotationClamp rty = new IrisAxisRotationClamp();
|
||||
rty.setEnabled(true);
|
||||
rty.setInterval(90);
|
||||
rty.minMax(y);
|
||||
rt.setYAxis(rty);
|
||||
return rt;
|
||||
}
|
||||
|
||||
public static IrisObjectRotation of(double x, double y, double z) {
|
||||
IrisObjectRotation rt = new IrisObjectRotation();
|
||||
IrisAxisRotationClamp rtx = new IrisAxisRotationClamp();
|
||||
|
||||
+77
@@ -1,11 +1,43 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.object.FloatingObjectFootprint;
|
||||
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MantleFloatingObjectComponentInvertedCountersTest {
|
||||
|
||||
private FloatingObjectFootprint footprint(int lowestSolidKeyY, int highestSolidKeyY, int tallestKx, int tallestKz) throws Exception {
|
||||
Constructor<FloatingObjectFootprint> constructor = FloatingObjectFootprint.class.getDeclaredConstructor(
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
long[].class
|
||||
);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(
|
||||
lowestSolidKeyY,
|
||||
highestSolidKeyY,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
tallestKx,
|
||||
tallestKz,
|
||||
99,
|
||||
99,
|
||||
new long[0]
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetObjectCounters_resetsAllInvertedCountersToZero() {
|
||||
MantleFloatingObjectComponent.objectsInvertedAttempted.set(5);
|
||||
@@ -39,4 +71,49 @@ public class MantleFloatingObjectComponentInvertedCountersTest {
|
||||
assertEquals(0, MantleFloatingObjectComponent.objectsAttempted.get());
|
||||
assertEquals(0, MantleFloatingObjectComponent.objectsPlaced.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invertedBaseY_anchorsOriginalLowestSolidBelowBottomFace() throws Exception {
|
||||
FloatingObjectFootprint footprint = footprint(5, 30, 2, 3);
|
||||
|
||||
assertEquals(104, MantleFloatingObjectComponent.invertedBaseY(100, footprint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invertedBaseX_usesTopFootprintAnchor() throws Exception {
|
||||
FloatingObjectFootprint footprint = footprint(5, 30, 2, 3);
|
||||
|
||||
assertEquals(106, MantleFloatingObjectComponent.invertedBaseX(100, 8, footprint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invertedBaseZ_mirrorsTopFootprintAnchor() throws Exception {
|
||||
FloatingObjectFootprint footprint = footprint(5, 30, 2, 3);
|
||||
|
||||
assertEquals(111, MantleFloatingObjectComponent.invertedBaseZ(100, 8, footprint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invertedBaseX_usesFixedYRotationAnchor() throws Exception {
|
||||
FloatingObjectFootprint footprint = footprint(5, 30, 2, 3);
|
||||
IrisObjectRotation rotation = IrisObjectRotation.xFlip180WithY(90);
|
||||
|
||||
assertEquals(111, MantleFloatingObjectComponent.invertedBaseX(100, 8, footprint, rotation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invertedBaseZ_usesFixedYRotationAnchor() throws Exception {
|
||||
FloatingObjectFootprint footprint = footprint(5, 30, 2, 3);
|
||||
IrisObjectRotation rotation = IrisObjectRotation.xFlip180WithY(90);
|
||||
|
||||
assertEquals(110, MantleFloatingObjectComponent.invertedBaseZ(100, 8, footprint, rotation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invertedBaseY_isStableAcrossFixedYRotation() throws Exception {
|
||||
FloatingObjectFootprint footprint = footprint(5, 30, 2, 3);
|
||||
IrisObjectRotation rotation = IrisObjectRotation.xFlip180WithY(270);
|
||||
|
||||
assertEquals(104, MantleFloatingObjectComponent.invertedBaseY(100, footprint, rotation));
|
||||
}
|
||||
}
|
||||
|
||||
+86
-1
@@ -1,5 +1,8 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.project.noise.NoiseGenerator;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -15,7 +18,9 @@ public class FloatingIslandSampleBottomYTest {
|
||||
}
|
||||
}
|
||||
for (boolean b : solidMask) {
|
||||
if (b) solidCount++;
|
||||
if (b) {
|
||||
solidCount++;
|
||||
}
|
||||
}
|
||||
return FloatingIslandSample.constructForTest(islandBaseY, solidMask.length, topIdx, solidCount, solidMask);
|
||||
}
|
||||
@@ -72,4 +77,84 @@ public class FloatingIslandSampleBottomYTest {
|
||||
assertEquals(false, mask[1]);
|
||||
assertEquals(false, mask[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundedEdgeHeight_zeroFade_removesEdgeWall() {
|
||||
assertEquals(0, FloatingIslandSample.roundedEdgeHeight(18, 0.0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundedEdgeDepth_zeroFade_removesMinimumTailAtEdge() {
|
||||
assertEquals(0, FloatingIslandSample.roundedEdgeDepth(10, 20, 1.0, 0.0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundedEdgeDepth_fullFade_keepsConfiguredDepth() {
|
||||
assertEquals(15, FloatingIslandSample.roundedEdgeDepth(10, 20, 0.5, 1.0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carveSolidInterior_preservesOuterShell() {
|
||||
boolean[] mask = {false, true, true, true, true, false};
|
||||
CNG carve = new CNG(new RNG(1), new NoiseGenerator() {
|
||||
@Override
|
||||
public double noise(double x) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return 1.0;
|
||||
}
|
||||
}, 1.0, 1);
|
||||
|
||||
int count = FloatingIslandSample.carveSolidInterior(mask, 100, 0, 0, carve, 0.5);
|
||||
|
||||
assertEquals(2, count);
|
||||
assertEquals(false, mask[0]);
|
||||
assertEquals(true, mask[1]);
|
||||
assertEquals(false, mask[2]);
|
||||
assertEquals(false, mask[3]);
|
||||
assertEquals(true, mask[4]);
|
||||
assertEquals(false, mask[5]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasFootprintNeighborSupport_rejectsSingleIsolatedColumn() {
|
||||
CNG footprint = new CNG(new RNG(2)) {
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return x == 0 && z == 0 ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return noise(x, z);
|
||||
}
|
||||
};
|
||||
|
||||
assertEquals(false, FloatingIslandSample.hasFootprintNeighborSupport(footprint, 0, 0, 0.0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasFootprintNeighborSupport_acceptsCardinalNeighbor() {
|
||||
CNG footprint = new CNG(new RNG(3)) {
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return (x == 0 && z == 0) || (x == 1 && z == 0) ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return noise(x, z);
|
||||
}
|
||||
};
|
||||
|
||||
assertEquals(true, FloatingIslandSample.hasFootprintNeighborSupport(footprint, 0, 0, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,33 @@ public class IrisObjectRotationFlipTest {
|
||||
assertTrue(!rot.canRotateY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xFlip180RandomY_canRotateY_returnsTrue() {
|
||||
IrisObjectRotation rot = IrisObjectRotation.xFlip180RandomY();
|
||||
assertTrue(rot.canRotateY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xFlip180WithY_zeroYaw_stillUsesFixedYRotation() {
|
||||
IrisObjectRotation rot = IrisObjectRotation.xFlip180WithY(0);
|
||||
BlockVector v = new BlockVector(1, 2, 3);
|
||||
BlockVector result = rot.rotate(v, 117, 253, 91);
|
||||
assertTrue(rot.canRotateY());
|
||||
assertEquals(1, result.getBlockX());
|
||||
assertEquals(-2, result.getBlockY());
|
||||
assertEquals(-3, result.getBlockZ());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xFlip180WithY_ninetyYaw_rotatesMirroredFootprint() {
|
||||
IrisObjectRotation rot = IrisObjectRotation.xFlip180WithY(90);
|
||||
BlockVector v = new BlockVector(2, 5, 3);
|
||||
BlockVector result = rot.rotate(v, 0, 0, 0);
|
||||
assertEquals(-3, result.getBlockX());
|
||||
assertEquals(-5, result.getBlockY());
|
||||
assertEquals(-2, result.getBlockZ());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xFlip180_rotateVector_negatesYandZ() {
|
||||
IrisObjectRotation rot = IrisObjectRotation.xFlip180();
|
||||
|
||||
+25
@@ -51,6 +51,31 @@ public class IslandObjectPlacerAnchorFaceTest {
|
||||
assertEquals(1, placer.getWritesDroppedAboveBottom());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bottomFace_canWriteObjectBlock_allowsBelowAnchorWithoutCounting() {
|
||||
FloatingIslandSample[] samples = new FloatingIslandSample[256];
|
||||
samples[0] = sampleWithBottomAt(100, 0);
|
||||
|
||||
IslandObjectPlacer placer = new IslandObjectPlacer(null, samples, 0, 0, 100, IslandObjectPlacer.AnchorFace.BOTTOM);
|
||||
|
||||
assertEquals(true, placer.canWriteObjectBlock(0, 99, 0));
|
||||
assertEquals(0, placer.getWritesAttempted());
|
||||
assertEquals(0, placer.getWritesDroppedAboveBottom());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bottomFace_canWriteObjectBlock_blocksAnchorAndAboveWithoutCounting() {
|
||||
FloatingIslandSample[] samples = new FloatingIslandSample[256];
|
||||
samples[0] = sampleWithBottomAt(100, 0);
|
||||
|
||||
IslandObjectPlacer placer = new IslandObjectPlacer(null, samples, 0, 0, 100, IslandObjectPlacer.AnchorFace.BOTTOM);
|
||||
|
||||
assertEquals(false, placer.canWriteObjectBlock(0, 100, 0));
|
||||
assertEquals(false, placer.canWriteObjectBlock(0, 101, 0));
|
||||
assertEquals(0, placer.getWritesAttempted());
|
||||
assertEquals(0, placer.getWritesDroppedAboveBottom());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topFace_existingConstructor_dropsBelowAnchor_noRegression() {
|
||||
FloatingIslandSample[] samples = new FloatingIslandSample[256];
|
||||
|
||||
Reference in New Issue
Block a user