mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-19 16:10:42 +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
|
||||||
|
|
||||||
Building Iris is fairly simple, though you will need to setup a few things if your system has never been used for java
|
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.
|
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) {
|
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 xf = x - minX;
|
||||||
int zf = z - minZ;
|
int zf = z - minZ;
|
||||||
if (xf >= 0 && xf < 16 && zf >= 0 && zf < 16) {
|
if (xf >= 0 && xf < 16 && zf >= 0 && zf < 16) {
|
||||||
@@ -136,27 +142,37 @@ public class IslandObjectPlacer implements IObjectPlacer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (y >= anchorY) {
|
if (y >= anchorY) {
|
||||||
writesDroppedAboveBottom++;
|
if (countWrite) {
|
||||||
|
writesDroppedAboveBottom++;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (face == AnchorFace.TOP) {
|
if (face == AnchorFace.TOP) {
|
||||||
if (y <= anchorY) {
|
if (y <= anchorY) {
|
||||||
writesDroppedBelow++;
|
if (countWrite) {
|
||||||
|
writesDroppedBelow++;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!overhangAllowed[idx]) {
|
if (!overhangAllowed[idx]) {
|
||||||
writesDroppedOverhang++;
|
if (countWrite) {
|
||||||
|
writesDroppedOverhang++;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (y >= anchorY) {
|
if (y >= anchorY) {
|
||||||
writesDroppedBottomOverhang++;
|
if (countWrite) {
|
||||||
|
writesDroppedBottomOverhang++;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!overhangAllowed[idx]) {
|
if (!overhangAllowed[idx]) {
|
||||||
writesDroppedBottomOverhang++;
|
if (countWrite) {
|
||||||
|
writesDroppedBottomOverhang++;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,19 +180,29 @@ public class IslandObjectPlacer implements IObjectPlacer {
|
|||||||
}
|
}
|
||||||
if (face == AnchorFace.TOP) {
|
if (face == AnchorFace.TOP) {
|
||||||
if (y <= anchorY) {
|
if (y <= anchorY) {
|
||||||
writesDroppedBelow++;
|
if (countWrite) {
|
||||||
|
writesDroppedBelow++;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (y >= anchorY) {
|
if (y >= anchorY) {
|
||||||
writesDroppedBottomOverhang++;
|
if (countWrite) {
|
||||||
|
writesDroppedBottomOverhang++;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writesDroppedOverhang++;
|
if (countWrite) {
|
||||||
|
writesDroppedOverhang++;
|
||||||
|
}
|
||||||
return true;
|
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) {
|
private @Nullable FloatingIslandSample sampleAt(int x, int z) {
|
||||||
int xf = x - minX;
|
int xf = x - minX;
|
||||||
int zf = z - minZ;
|
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.modifier.IrisFloatingChildBiomeModifier;
|
||||||
import art.arcane.iris.engine.object.FloatingIslandSample;
|
import art.arcane.iris.engine.object.FloatingIslandSample;
|
||||||
import art.arcane.iris.engine.object.FloatingObjectFootprint;
|
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.IrisBiome;
|
||||||
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
||||||
import art.arcane.iris.engine.object.IrisObject;
|
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.IrisObjectRotation;
|
||||||
import art.arcane.iris.engine.object.IrisObjectTranslate;
|
import art.arcane.iris.engine.object.IrisObjectTranslate;
|
||||||
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
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.iris.util.project.context.ChunkContext;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
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;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
@ComponentFlag(ReservedFlag.FLOATING_OBJECT)
|
@ComponentFlag(ReservedFlag.FLOATING_OBJECT)
|
||||||
@@ -53,7 +65,6 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
public static final AtomicLong objectsRelaxed = new AtomicLong();
|
public static final AtomicLong objectsRelaxed = new AtomicLong();
|
||||||
public static final AtomicLong objectsSkippedShrink = new AtomicLong();
|
public static final AtomicLong objectsSkippedShrink = new AtomicLong();
|
||||||
public static final AtomicLong objectsSkippedNullObj = 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 writesAttemptedTotal = new AtomicLong();
|
||||||
public static final AtomicLong writesDroppedBelowTotal = new AtomicLong();
|
public static final AtomicLong writesDroppedBelowTotal = new AtomicLong();
|
||||||
public static final AtomicLong writesDroppedOverhangTotal = 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 objectsInvertedSkippedNullObj = new AtomicLong();
|
||||||
public static final AtomicLong writesDroppedAboveBottomTotal = new AtomicLong();
|
public static final AtomicLong writesDroppedAboveBottomTotal = new AtomicLong();
|
||||||
public static final AtomicLong writesDroppedBottomOverhangTotal = 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 MIN_FOOTPRINT_CELLS_CHECKED = 3;
|
||||||
|
private static final int INVERTED_PICK_ATTEMPTS = 8;
|
||||||
private static final IrisObjectRotation ROTATION_NONE = IrisObjectRotation.of(0, 0, 0);
|
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) {
|
public MantleFloatingObjectComponent(EngineMantle engineMantle) {
|
||||||
super(engineMantle, ReservedFlag.FLOATING_OBJECT, 2);
|
super(engineMantle, ReservedFlag.FLOATING_OBJECT, 2);
|
||||||
@@ -85,11 +93,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
objectsRelaxed.set(0);
|
objectsRelaxed.set(0);
|
||||||
objectsSkippedShrink.set(0);
|
objectsSkippedShrink.set(0);
|
||||||
objectsSkippedNullObj.set(0);
|
objectsSkippedNullObj.set(0);
|
||||||
terrainMismatchWarnings.set(0);
|
|
||||||
writesAttemptedTotal.set(0);
|
writesAttemptedTotal.set(0);
|
||||||
writesDroppedBelowTotal.set(0);
|
writesDroppedBelowTotal.set(0);
|
||||||
writesDroppedOverhangTotal.set(0);
|
writesDroppedOverhangTotal.set(0);
|
||||||
heavyClipWarnings.set(0);
|
|
||||||
anchorYHisto.clear();
|
anchorYHisto.clear();
|
||||||
objectsInvertedAttempted.set(0);
|
objectsInvertedAttempted.set(0);
|
||||||
objectsInvertedPlaced.set(0);
|
objectsInvertedPlaced.set(0);
|
||||||
@@ -101,26 +107,13 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
writesDroppedBottomOverhangTotal.set(0);
|
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 attempted = islandPlacer.getWritesAttempted();
|
||||||
int below = islandPlacer.getWritesDroppedBelow();
|
int below = islandPlacer.getWritesDroppedBelow();
|
||||||
int overhang = islandPlacer.getWritesDroppedOverhang();
|
int overhang = islandPlacer.getWritesDroppedOverhang();
|
||||||
writesAttemptedTotal.addAndGet(attempted);
|
writesAttemptedTotal.addAndGet(attempted);
|
||||||
writesDroppedBelowTotal.addAndGet(below);
|
writesDroppedBelowTotal.addAndGet(below);
|
||||||
writesDroppedOverhangTotal.addAndGet(overhang);
|
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) {
|
private static void recordInvertedWriteStats(IslandObjectPlacer islandPlacer) {
|
||||||
@@ -128,34 +121,6 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
writesDroppedBottomOverhangTotal.addAndGet(islandPlacer.getWritesDroppedBottomOverhang());
|
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
|
@Override
|
||||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||||
IrisComplex complex = context.getComplex();
|
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++) {
|
for (int i = 0; i < 256; i++) {
|
||||||
FloatingIslandSample s = samples[i];
|
FloatingIslandSample s = samples[i];
|
||||||
if (s == null || s.entry == null) {
|
if (s == null || s.entry == null) {
|
||||||
@@ -193,7 +158,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
entryColumns.computeIfAbsent(s.entry, e -> new KList<>()).add(i);
|
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();
|
IrisFloatingChildBiomes entry = ec.getKey();
|
||||||
KList<Integer> columns = ec.getValue();
|
KList<Integer> columns = ec.getValue();
|
||||||
if (columns.isEmpty()) {
|
if (columns.isEmpty()) {
|
||||||
@@ -280,7 +245,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
try {
|
try {
|
||||||
obj.place(xx, -1, zz, writer, floatingPlacement, rng, (b, bd) -> {
|
obj.place(xx, -1, zz, writer, floatingPlacement, rng, (b, bd) -> {
|
||||||
String marker = placementMarker(obj, id);
|
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);
|
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||||
}
|
}
|
||||||
}, null, data);
|
}, null, data);
|
||||||
@@ -367,16 +332,15 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
try {
|
try {
|
||||||
obj.place(wx, yv, wz, islandPlacer, anchored, rng, (b, bd) -> {
|
obj.place(wx, yv, wz, islandPlacer, anchored, rng, (b, bd) -> {
|
||||||
String marker = placementMarker(obj, id);
|
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);
|
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||||
}
|
}
|
||||||
}, null, data);
|
}, null, data);
|
||||||
objectsPlaced.incrementAndGet();
|
objectsPlaced.incrementAndGet();
|
||||||
recordAnchorYHisto(pickTopY);
|
recordAnchorYHisto(pickTopY);
|
||||||
int trunkWx = minX + pickedXf;
|
recordWriteStats(islandPlacer);
|
||||||
int trunkWz = minZ + pickedZf;
|
|
||||||
verifyTerrainBelowObject(obj, trunkWx, trunkWz, pickTopY, pickedSample);
|
|
||||||
recordWriteStats(obj, trunkWx, trunkWz, pickTopY, islandPlacer);
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.reportError(e);
|
Iris.reportError(e);
|
||||||
}
|
}
|
||||||
@@ -417,44 +381,56 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
final IrisObject obj = obj0;
|
final IrisObject obj = obj0;
|
||||||
|
|
||||||
FloatingObjectFootprint fp = FloatingObjectFootprint.compute(obj);
|
FloatingObjectFootprint fp = FloatingObjectFootprint.compute(obj);
|
||||||
|
int invertedYRotation = rng.i(0, 3) * 90;
|
||||||
|
IrisObjectRotation invertedRotation = IrisObjectRotation.xFlip180WithY(invertedYRotation);
|
||||||
|
|
||||||
KList<Integer> pool = interior.isEmpty() ? columns : interior;
|
KList<Integer> pool = interior.isEmpty() ? columns : interior;
|
||||||
if (interior.isEmpty()) {
|
if (interior.isEmpty()) {
|
||||||
objectsInvertedFallbackNoInterior.incrementAndGet();
|
objectsInvertedFallbackNoInterior.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
int pickedKey = pool.get(rng.i(0, pool.size() - 1));
|
int pickedXf = -1;
|
||||||
int pickedXf = pickedKey & 15;
|
int pickedZf = -1;
|
||||||
int pickedZf = pickedKey >> 4;
|
int pickBottomY = -1;
|
||||||
FloatingIslandSample pickedSample = samples[(pickedZf << 4) | pickedXf];
|
boolean foundBottomAnchor = false;
|
||||||
if (pickedSample == null) {
|
for (int attempt = 0; attempt < INVERTED_PICK_ATTEMPTS; attempt++) {
|
||||||
objectsInvertedSkippedNoFlat.incrementAndGet();
|
int pickedKey = pool.get(rng.i(0, pool.size() - 1));
|
||||||
continue;
|
int candidateXf = pickedKey & 15;
|
||||||
}
|
int candidateZf = pickedKey >> 4;
|
||||||
int pickBottomY = pickedSample.bottomY();
|
FloatingIslandSample candidateSample = samples[(candidateZf << 4) | candidateXf];
|
||||||
if (pickBottomY < 0) {
|
if (candidateSample == null) {
|
||||||
objectsInvertedSkippedNoFlat.incrementAndGet();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFootprintFlatBottom(fp, pickedXf, pickedZf, pickBottomY, samples, 2)) {
|
|
||||||
if (!isFootprintFlatBottom(fp, pickedXf, pickedZf, pickBottomY, samples, 4)) {
|
|
||||||
objectsInvertedSkippedNoFlat.incrementAndGet();
|
|
||||||
continue;
|
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 wx = invertedBaseX(minX, pickedXf, fp, invertedRotation);
|
||||||
int wz = minZ + pickedZf - fp.getTallestKzBottom();
|
int wz = invertedBaseZ(minZ, pickedZf, fp, invertedRotation);
|
||||||
|
|
||||||
IrisObjectPlacement inverted = placement.toPlacement(obj.getLoadKey());
|
IrisObjectPlacement inverted = placement.toPlacement(obj.getLoadKey());
|
||||||
inverted.setMode(translateStiltModeForFloating(inverted.getMode()));
|
inverted.setMode(translateStiltModeForFloating(inverted.getMode()));
|
||||||
inverted.setTranslate(new IrisObjectTranslate());
|
inverted.setTranslate(new IrisObjectTranslate());
|
||||||
inverted.setRotation(IrisObjectRotation.xFlip180());
|
inverted.setRotation(invertedRotation);
|
||||||
inverted.setForcePlace(true);
|
inverted.setForcePlace(true);
|
||||||
inverted.setBottom(false);
|
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);
|
IslandObjectPlacer islandPlacer = new IslandObjectPlacer(writer, samples, minX, minZ, pickBottomY, IslandObjectPlacer.AnchorFace.BOTTOM);
|
||||||
int id = rng.i(0, Integer.MAX_VALUE);
|
int id = rng.i(0, Integer.MAX_VALUE);
|
||||||
@@ -462,7 +438,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
try {
|
try {
|
||||||
obj.place(wx, yv, wz, islandPlacer, inverted, rng, (b, bd) -> {
|
obj.place(wx, yv, wz, islandPlacer, inverted, rng, (b, bd) -> {
|
||||||
String marker = placementMarker(obj, id);
|
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);
|
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||||
}
|
}
|
||||||
}, null, data);
|
}, 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) {
|
private static boolean isFootprintFlatBottom(FloatingObjectFootprint fp, IrisObjectRotation rotation, int pickedXf, int pickedZf, int pickBottomY, FloatingIslandSample[] samples, int tolerance) {
|
||||||
int tallestKxBottom = fp.getTallestKxBottom();
|
BlockVector anchor = invertedFootprintAnchor(fp, rotation);
|
||||||
int tallestKzBottom = fp.getTallestKzBottom();
|
|
||||||
int checked = 0;
|
int checked = 0;
|
||||||
boolean touchedChunkEdge = false;
|
boolean touchedChunkEdge = false;
|
||||||
long[] cells = fp.footprintXZ();
|
long[] cells = fp.footprintXZ();
|
||||||
@@ -484,8 +461,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
long encoded = cells[i];
|
long encoded = cells[i];
|
||||||
int kx = (int) (encoded >> 32);
|
int kx = (int) (encoded >> 32);
|
||||||
int kz = (int) (encoded & 0xFFFFFFFFL);
|
int kz = (int) (encoded & 0xFFFFFFFFL);
|
||||||
int colXf = pickedXf + (kx - tallestKxBottom);
|
BlockVector cell = rotation.rotate(new BlockVector(kx, 0, kz), 0, 0, 0);
|
||||||
int colZf = pickedZf + (kz - tallestKzBottom);
|
int colXf = pickedXf + cell.getBlockX() - anchor.getBlockX();
|
||||||
|
int colZf = pickedZf + cell.getBlockZ() - anchor.getBlockZ();
|
||||||
if (colXf < 0 || colXf >= 16 || colZf < 0 || colZf >= 16) {
|
if (colXf < 0 || colXf >= 16 || colZf < 0 || colZf >= 16) {
|
||||||
touchedChunkEdge = true;
|
touchedChunkEdge = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -506,6 +484,48 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
return touchedChunkEdge;
|
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) {
|
private static boolean isFootprintFlat(FloatingObjectFootprint fp, int pickedXf, int pickedZf, int pickTopY, FloatingIslandSample[] samples, int tolerance) {
|
||||||
int tallestKx = fp.getTallestKx();
|
int tallestKx = fp.getTallestKx();
|
||||||
int tallestKz = fp.getTallestKz();
|
int tallestKz = fp.getTallestKz();
|
||||||
@@ -556,10 +576,18 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
if (xf <= 0 || xf >= 15 || zf <= 0 || zf >= 15) {
|
if (xf <= 0 || xf >= 15 || zf <= 0 || zf >= 15) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (samples[(zf << 4) | (xf + 1)] == null) continue;
|
if (samples[(zf << 4) | (xf + 1)] == null) {
|
||||||
if (samples[(zf << 4) | (xf - 1)] == null) continue;
|
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 + 1) << 4) | xf] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (samples[((zf - 1) << 4) | xf] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
interior.add(key);
|
interior.add(key);
|
||||||
}
|
}
|
||||||
return interior;
|
return interior;
|
||||||
@@ -592,7 +620,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
@Override
|
@Override
|
||||||
protected int computeRadius() {
|
protected int computeRadius() {
|
||||||
int maxObjectExtent = 0;
|
int maxObjectExtent = 0;
|
||||||
java.util.Set<String> objectKeys = new java.util.HashSet<>();
|
Set<String> objectKeys = new HashSet<>();
|
||||||
try {
|
try {
|
||||||
IrisData data = getData();
|
IrisData data = getData();
|
||||||
for (IrisBiome biome : getDimension().getAllBiomes(this::getData)) {
|
for (IrisBiome biome : getDimension().getAllBiomes(this::getData)) {
|
||||||
@@ -617,11 +645,15 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
}
|
}
|
||||||
for (String key : objectKeys) {
|
for (String key : objectKeys) {
|
||||||
try {
|
try {
|
||||||
java.io.File f = data.getObjectLoader().findFile(key);
|
File f = data.getObjectLoader().findFile(key);
|
||||||
if (f == null) continue;
|
if (f == null) {
|
||||||
org.bukkit.util.BlockVector sz = IrisObject.sampleSize(f);
|
continue;
|
||||||
|
}
|
||||||
|
BlockVector sz = IrisObject.sampleSize(f);
|
||||||
int extent = Math.max(sz.getBlockX(), sz.getBlockZ());
|
int extent = Math.max(sz.getBlockX(), sz.getBlockZ());
|
||||||
if (extent > maxObjectExtent) maxObjectExtent = extent;
|
if (extent > maxObjectExtent) {
|
||||||
|
maxObjectExtent = extent;
|
||||||
|
}
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,10 +662,14 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
return Math.max(16, maxObjectExtent);
|
return Math.max(16, maxObjectExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectPlacementKeys(KList<IrisObjectPlacement> placements, java.util.Set<String> out) {
|
private static void collectPlacementKeys(KList<IrisObjectPlacement> placements, Set<String> out) {
|
||||||
if (placements == null) return;
|
if (placements == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (IrisObjectPlacement p : placements) {
|
for (IrisObjectPlacement p : placements) {
|
||||||
if (p == null || p.getPlace() == null) continue;
|
if (p == null || p.getPlace() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
out.addAll(p.getPlace());
|
out.addAll(p.getPlace());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+114
-118
@@ -18,25 +18,29 @@
|
|||||||
|
|
||||||
package art.arcane.iris.engine.modifier;
|
package art.arcane.iris.engine.modifier;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.nms.INMS;
|
import art.arcane.iris.core.nms.INMS;
|
||||||
import art.arcane.iris.engine.IrisComplex;
|
import art.arcane.iris.engine.IrisComplex;
|
||||||
import art.arcane.iris.engine.decorator.FloatingDecorator;
|
import art.arcane.iris.engine.decorator.FloatingDecorator;
|
||||||
import art.arcane.iris.engine.decorator.IrisSeaSurfaceDecorator;
|
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.Engine;
|
||||||
import art.arcane.iris.engine.framework.EngineAssignedModifier;
|
import art.arcane.iris.engine.framework.EngineAssignedModifier;
|
||||||
import art.arcane.iris.engine.framework.EngineDecorator;
|
import art.arcane.iris.engine.framework.EngineDecorator;
|
||||||
import art.arcane.iris.engine.mantle.components.MantleFloatingObjectComponent;
|
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.FloatingIslandSample;
|
||||||
import art.arcane.iris.engine.object.IrisBiome;
|
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.IrisBiomeCustom;
|
||||||
import art.arcane.iris.engine.object.IrisDecorationPart;
|
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||||
import art.arcane.iris.engine.object.IrisDimension;
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
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.common.data.B;
|
||||||
import art.arcane.iris.util.project.context.ChunkContext;
|
import art.arcane.iris.util.project.context.ChunkContext;
|
||||||
import art.arcane.iris.util.project.hunk.Hunk;
|
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.collection.KList;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.volmlib.util.matter.MatterBiomeInject;
|
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.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
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 class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<BlockData> {
|
||||||
public static final long FLOATING_BASE_SEED_SALT = 0x5EED_F107_00F1B10CL;
|
public static final long FLOATING_BASE_SEED_SALT = 0x5EED_F107_00F1B10CL;
|
||||||
private static final AtomicLong columnsChecked = new AtomicLong();
|
private static final Runnable NOOP_DECORATION_MISS = () -> {
|
||||||
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 final RNG rng;
|
private final RNG rng;
|
||||||
private final EngineDecorator seaSurfaceDecorator;
|
private final EngineDecorator seaSurfaceDecorator;
|
||||||
|
|
||||||
public static void reportFloatingStats() {
|
private static KList<BlockData> generateBottomPaletteLayers(IrisFloatingChildBiomes entry, IrisDimension dimension, double wx, double wz, RNG random, int paletteDepth, IrisData data, IrisComplex complex) {
|
||||||
StringBuilder topFloors = new StringBuilder();
|
if (entry == null || entry.getBottomPaletteMode() != FloatingBottomPaletteMode.CUSTOM || entry.getBottomPalette() == null || entry.getBottomPalette().isEmpty()) {
|
||||||
floorMatHisto.entrySet().stream()
|
return null;
|
||||||
.sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
|
}
|
||||||
.limit(5)
|
return generatePaletteLayers(dimension, entry.getBottomPalette(), wx, wz, random.nextParallelRNG(0xB0770B), paletteDepth, data, complex);
|
||||||
.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 void maybeReport() {
|
private static KList<BlockData> generatePaletteLayers(IrisDimension dimension, KList<IrisBiomePaletteLayer> layers, double wx, double wz, RNG random, int maxDepth, IrisData data, IrisComplex complex) {
|
||||||
long now = System.currentTimeMillis();
|
KList<BlockData> generated = new KList<>();
|
||||||
long last = lastReportMs.get();
|
if (layers == null || layers.isEmpty() || maxDepth <= 0) {
|
||||||
if (now - last >= 10000L && lastReportMs.compareAndSet(last, now)) {
|
return generated;
|
||||||
reportFloatingStats();
|
}
|
||||||
if (reportCycle.incrementAndGet() >= 30) {
|
|
||||||
reportCycle.set(0);
|
int generatorSeed = 7235;
|
||||||
resetAllCounters();
|
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() {
|
private static boolean usesBottomPalette(IrisFloatingChildBiomes entry) {
|
||||||
columnsChecked.set(0);
|
FloatingBottomPaletteMode mode = entry == null || entry.getBottomPaletteMode() == null ? FloatingBottomPaletteMode.DEPTH : entry.getBottomPaletteMode();
|
||||||
samplesAccepted.set(0);
|
return mode != FloatingBottomPaletteMode.DEPTH;
|
||||||
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 void recordFloorMat(String matKey) {
|
private static int[] bottomDepths(FloatingIslandSample sample, int chunkHeight) {
|
||||||
if (floorMatHisto.size() < 32) {
|
int[] bottomDepths = new int[sample.solidMask.length];
|
||||||
floorMatHisto.computeIfAbsent(matKey, k -> new AtomicLong()).incrementAndGet();
|
for (int i = 0; i < bottomDepths.length; i++) {
|
||||||
} else {
|
bottomDepths[i] = -1;
|
||||||
AtomicLong existing = floorMatHisto.get(matKey);
|
|
||||||
if (existing != null) {
|
|
||||||
existing.incrementAndGet();
|
|
||||||
} else {
|
|
||||||
floorMatHisto.computeIfAbsent("other", k -> new AtomicLong()).incrementAndGet();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
public IrisFloatingChildBiomeModifier(Engine engine) {
|
||||||
@@ -174,13 +193,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
columnsChecked.incrementAndGet();
|
|
||||||
|
|
||||||
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, getEngine());
|
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, getEngine());
|
||||||
if (sample == null) {
|
if (sample == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
samplesAccepted.incrementAndGet();
|
|
||||||
|
|
||||||
IrisFloatingChildBiomes entry = sample.entry;
|
IrisFloatingChildBiomes entry = sample.entry;
|
||||||
IrisBiome target = entry.getRealBiome(parent, data);
|
IrisBiome target = entry.getRealBiome(parent, data);
|
||||||
@@ -191,8 +207,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
if (blocks == null || blocks.isEmpty()) {
|
if (blocks == null || blocks.isEmpty()) {
|
||||||
blocks = parent.generateLayers(dimension, wx, wz, layerRng, paletteDepth, paletteDepth, data, complex);
|
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");
|
BlockData fallbackSolid = B.get("minecraft:stone");
|
||||||
|
|
||||||
|
int[] bottomDepths = usesBottomPalette(entry) ? bottomDepths(sample, chunkHeight) : null;
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
for (int k = sample.topIdx; k >= 0; k--) {
|
for (int k = sample.topIdx; k >= 0; k--) {
|
||||||
if (!sample.solidMask[k]) {
|
if (!sample.solidMask[k]) {
|
||||||
@@ -202,13 +220,8 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
if (y < 0 || y >= chunkHeight) {
|
if (y < 0 || y >= chunkHeight) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BlockData block = null;
|
int bottomDepth = bottomDepths == null || bottomDepths[k] < 0 ? depth : bottomDepths[k];
|
||||||
if (blocks != null && !blocks.isEmpty()) {
|
BlockData block = selectPaletteBlock(entry, blocks, bottomBlocks, depth, bottomDepth, fallbackSolid);
|
||||||
block = blocks.hasIndex(depth) ? blocks.get(depth) : blocks.getLast();
|
|
||||||
}
|
|
||||||
if (block == null) {
|
|
||||||
block = fallbackSolid;
|
|
||||||
}
|
|
||||||
if (block != null) {
|
if (block != null) {
|
||||||
output.set(xf, y, zf, block);
|
output.set(xf, y, zf, block);
|
||||||
}
|
}
|
||||||
@@ -270,12 +283,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
if (sample == null) {
|
if (sample == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
decoratePhaseColumns.incrementAndGet();
|
|
||||||
IrisFloatingChildBiomes entry = sample.entry;
|
IrisFloatingChildBiomes entry = sample.entry;
|
||||||
IrisBiome target = entry.getRealBiome(parent, data);
|
IrisBiome target = entry.getRealBiome(parent, data);
|
||||||
|
|
||||||
if (!entry.isInheritDecorators() || target == null) {
|
if (!entry.isInheritDecorators() || target == null) {
|
||||||
decorateSkippedNoInherit.incrementAndGet();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,26 +295,12 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
if (topY + 1 < chunkHeight) {
|
if (topY + 1 < chunkHeight) {
|
||||||
BlockData above = output.get(xf, topY + 1, zf);
|
BlockData above = output.get(xf, topY + 1, zf);
|
||||||
if (above == null || B.isAir(above)) {
|
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 {
|
try {
|
||||||
RNG colRng = rng.nextParallelRNG((int) FloatingIslandSample.columnSeed(baseSeed, wx, wz));
|
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);
|
FloatingDecorator.decorateColumn(getEngine(), target, IrisDecorationPart.NONE, xf, zf, wx, wz, topY, max, output, colRng, NOOP_DECORATION_MISS);
|
||||||
if (placed > 0) {
|
|
||||||
decoratePlaced.addAndGet(placed);
|
|
||||||
} else {
|
|
||||||
decorateNoChange.incrementAndGet();
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
art.arcane.iris.Iris.reportError(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) {
|
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_COUNT = 7;
|
||||||
public static final int REJECT_CLUSTER = REJECT_NO_SEED;
|
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<int[]> LAST_REJECT = ThreadLocal.withInitial(() -> new int[1]);
|
||||||
private static final ThreadLocal<double[]> LAST_DENSITY = ThreadLocal.withInitial(() -> new double[2]);
|
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);
|
private static final ThreadLocal<HashMap<Long, FloatingIslandSample>> CHUNK_MEMO = ThreadLocal.withInitial(HashMap::new);
|
||||||
@@ -169,6 +170,9 @@ public final class FloatingIslandSample {
|
|||||||
if (signed <= signedCut) {
|
if (signed <= signedCut) {
|
||||||
return reject(REJECT_NO_SEED);
|
return reject(REJECT_NO_SEED);
|
||||||
}
|
}
|
||||||
|
if (!hasFootprintNeighborSupport(footprintCng, wx, wz, signedCut)) {
|
||||||
|
return reject(REJECT_NO_SEED);
|
||||||
|
}
|
||||||
|
|
||||||
CNG altitudeCng = entry.getAltitudeCng(baseSeed, data);
|
CNG altitudeCng = entry.getAltitudeCng(baseSeed, data);
|
||||||
if (altitudeCng == null) {
|
if (altitudeCng == null) {
|
||||||
@@ -182,14 +186,11 @@ public final class FloatingIslandSample {
|
|||||||
int maxAlt = Math.max(minAlt, entry.getMaxHeightAboveSurface() - worldMin);
|
int maxAlt = Math.max(minAlt, entry.getMaxHeightAboveSurface() - worldMin);
|
||||||
int baseY = minAlt + (int) Math.round(altClamped * (maxAlt - minAlt));
|
int baseY = minAlt + (int) Math.round(altClamped * (maxAlt - minAlt));
|
||||||
|
|
||||||
|
double edgeFade = edgeFade(signed, signedCut);
|
||||||
IrisBiome target = entry.getRealBiome(parent, data);
|
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;
|
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);
|
CNG bottomCng = entry.getBottomCng(baseSeed, data);
|
||||||
if (bottomCng == null) {
|
if (bottomCng == null) {
|
||||||
warnNullCng("bottomStyle", parent);
|
warnNullCng("bottomStyle", parent);
|
||||||
@@ -200,7 +201,7 @@ public final class FloatingIslandSample {
|
|||||||
double bottomShaped = Math.pow(bottomClamped, Math.max(0.1, entry.getBottomExponent()));
|
double bottomShaped = Math.pow(bottomClamped, Math.max(0.1, entry.getBottomExponent()));
|
||||||
int minDepth = Math.max(0, entry.getBottomDepthMin());
|
int minDepth = Math.max(0, entry.getBottomDepthMin());
|
||||||
int maxDepth = Math.max(minDepth, entry.getBottomDepthMax());
|
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;
|
int botY = baseY - depth;
|
||||||
|
|
||||||
Integer minAbsoluteY = entry.getMinAbsoluteY();
|
Integer minAbsoluteY = entry.getMinAbsoluteY();
|
||||||
@@ -242,8 +243,6 @@ public final class FloatingIslandSample {
|
|||||||
double carveThreshold = entry.getCarveThreshold();
|
double carveThreshold = entry.getCarveThreshold();
|
||||||
boolean useWarp = wallWarp != null && warpAmp > 0;
|
boolean useWarp = wallWarp != null && warpAmp > 0;
|
||||||
boolean useCarve = carve != null && carveThreshold < 1.0;
|
boolean useCarve = carve != null && carveThreshold < 1.0;
|
||||||
int solidCount = 0;
|
|
||||||
int highestSolidIdx = -1;
|
|
||||||
|
|
||||||
for (int k = 0; k < thickness; k++) {
|
for (int k = 0; k < thickness; k++) {
|
||||||
int wy = botY + k;
|
int wy = botY + k;
|
||||||
@@ -262,24 +261,14 @@ public final class FloatingIslandSample {
|
|||||||
if (layerSigned <= signedCut) {
|
if (layerSigned <= signedCut) {
|
||||||
continue;
|
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;
|
solidMask[k] = true;
|
||||||
solidCount++;
|
|
||||||
if (k > highestSolidIdx) {
|
|
||||||
highestSolidIdx = k;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useCarve) {
|
int solidCount = solidifyUncarvedInterior(solidMask);
|
||||||
solidCount = solidifyUncarvedInterior(solidMask);
|
if (useCarve) {
|
||||||
highestSolidIdx = highestSolidIndex(solidMask);
|
solidCount = carveSolidInterior(solidMask, botY, wx, wz, carve, carveThreshold);
|
||||||
}
|
}
|
||||||
|
int highestSolidIdx = highestSolidIndex(solidMask);
|
||||||
|
|
||||||
if (solidCount == 0 || highestSolidIdx < 0) {
|
if (solidCount == 0 || highestSolidIdx < 0) {
|
||||||
return reject(REJECT_NO_SOLID);
|
return reject(REJECT_NO_SOLID);
|
||||||
@@ -291,6 +280,80 @@ public final class FloatingIslandSample {
|
|||||||
return new FloatingIslandSample(entry, botY, thickness, topIdx, solidCount, solidMask);
|
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) {
|
static int solidifyUncarvedInterior(boolean[] solidMask) {
|
||||||
int firstSolid = -1;
|
int firstSolid = -1;
|
||||||
int lastSolid = -1;
|
int lastSolid = -1;
|
||||||
|
|||||||
@@ -18,21 +18,16 @@
|
|||||||
|
|
||||||
package art.arcane.iris.engine.object;
|
package art.arcane.iris.engine.object;
|
||||||
|
|
||||||
import art.arcane.iris.Iris;
|
|
||||||
import art.arcane.iris.util.common.data.B;
|
import art.arcane.iris.util.common.data.B;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
import org.bukkit.util.BlockVector;
|
import org.bukkit.util.BlockVector;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class FloatingObjectFootprint {
|
public class FloatingObjectFootprint {
|
||||||
private static final ConcurrentHashMap<String, FloatingObjectFootprint> CACHE = new ConcurrentHashMap<>();
|
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 lowestSolidKeyY;
|
||||||
private final int highestSolidKeyY;
|
private final int highestSolidKeyY;
|
||||||
@@ -111,98 +106,9 @@ public class FloatingObjectFootprint {
|
|||||||
int tallestKz = columnStats.isEmpty() ? 0 : (int) (tallestPacked & 0xFFFFFFFFL);
|
int tallestKz = columnStats.isEmpty() ? 0 : (int) (tallestPacked & 0xFFFFFFFFL);
|
||||||
int tallestKxBottom = columnStats.isEmpty() ? 0 : globalHighestKx[0];
|
int tallestKxBottom = columnStats.isEmpty() ? 0 : globalHighestKx[0];
|
||||||
int tallestKzBottom = columnStats.isEmpty() ? 0 : globalHighestKz[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);
|
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) {
|
private static long resolveTallestColumn(Map<Long, int[]> columnStats) {
|
||||||
long bestPacked = 0L;
|
long bestPacked = 0L;
|
||||||
int tallestCount = 0;
|
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.")
|
@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;
|
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)
|
@MinNumber(1)
|
||||||
@MaxNumber(512)
|
@MaxNumber(512)
|
||||||
@Desc("Hard cap on the total Y-extent (top minus bottom) of a single island column. Safety limit.")
|
@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;
|
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) {
|
public static IrisObjectRotation of(double x, double y, double z) {
|
||||||
IrisObjectRotation rt = new IrisObjectRotation();
|
IrisObjectRotation rt = new IrisObjectRotation();
|
||||||
IrisAxisRotationClamp rtx = new IrisAxisRotationClamp();
|
IrisAxisRotationClamp rtx = new IrisAxisRotationClamp();
|
||||||
|
|||||||
+77
@@ -1,11 +1,43 @@
|
|||||||
package art.arcane.iris.engine.mantle.components;
|
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 org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class MantleFloatingObjectComponentInvertedCountersTest {
|
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
|
@Test
|
||||||
public void resetObjectCounters_resetsAllInvertedCountersToZero() {
|
public void resetObjectCounters_resetsAllInvertedCountersToZero() {
|
||||||
MantleFloatingObjectComponent.objectsInvertedAttempted.set(5);
|
MantleFloatingObjectComponent.objectsInvertedAttempted.set(5);
|
||||||
@@ -39,4 +71,49 @@ public class MantleFloatingObjectComponentInvertedCountersTest {
|
|||||||
assertEquals(0, MantleFloatingObjectComponent.objectsAttempted.get());
|
assertEquals(0, MantleFloatingObjectComponent.objectsAttempted.get());
|
||||||
assertEquals(0, MantleFloatingObjectComponent.objectsPlaced.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;
|
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 org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -15,7 +18,9 @@ public class FloatingIslandSampleBottomYTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (boolean b : solidMask) {
|
for (boolean b : solidMask) {
|
||||||
if (b) solidCount++;
|
if (b) {
|
||||||
|
solidCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return FloatingIslandSample.constructForTest(islandBaseY, solidMask.length, topIdx, solidCount, solidMask);
|
return FloatingIslandSample.constructForTest(islandBaseY, solidMask.length, topIdx, solidCount, solidMask);
|
||||||
}
|
}
|
||||||
@@ -72,4 +77,84 @@ public class FloatingIslandSampleBottomYTest {
|
|||||||
assertEquals(false, mask[1]);
|
assertEquals(false, mask[1]);
|
||||||
assertEquals(false, mask[2]);
|
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());
|
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
|
@Test
|
||||||
public void xFlip180_rotateVector_negatesYandZ() {
|
public void xFlip180_rotateVector_negatesYandZ() {
|
||||||
IrisObjectRotation rot = IrisObjectRotation.xFlip180();
|
IrisObjectRotation rot = IrisObjectRotation.xFlip180();
|
||||||
|
|||||||
+25
@@ -51,6 +51,31 @@ public class IslandObjectPlacerAnchorFaceTest {
|
|||||||
assertEquals(1, placer.getWritesDroppedAboveBottom());
|
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
|
@Test
|
||||||
public void topFace_existingConstructor_dropsBelowAnchor_noRegression() {
|
public void topFace_existingConstructor_dropsBelowAnchor_noRegression() {
|
||||||
FloatingIslandSample[] samples = new FloatingIslandSample[256];
|
FloatingIslandSample[] samples = new FloatingIslandSample[256];
|
||||||
|
|||||||
Reference in New Issue
Block a user