Opti pass 1

This commit is contained in:
Brian Neumann-Fopiano
2026-04-15 12:50:12 -04:00
parent 9a231b2bcf
commit dcb3306197
43 changed files with 1832 additions and 410 deletions
+1 -1
View File
@@ -1 +1 @@
149256635 -1511497018
@@ -195,7 +195,6 @@ public class IrisPregenerator {
private void init() { private void init() {
generator.init(); generator.init();
generator.save();
} }
private void shutdown() { private void shutdown() {
@@ -20,6 +20,7 @@ package art.arcane.iris.core.pregenerator;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
import art.arcane.volmlib.util.math.Position2; import art.arcane.volmlib.util.math.Position2;
import art.arcane.volmlib.util.math.Spiraled; import art.arcane.volmlib.util.math.Spiraled;
import art.arcane.volmlib.util.math.Spiraler; import art.arcane.volmlib.util.math.Spiraler;
@@ -31,8 +32,7 @@ import java.util.Comparator;
@Builder @Builder
@Data @Data
public class PregenTask { public class PregenTask {
private static final Position2 ZERO = new Position2(0, 0); private static final KMap<Long, int[]> ORDERS = new KMap<>();
private static final KMap<Position2, KList<Position2>> ORDERS = new KMap<>();
@Builder.Default @Builder.Default
private final boolean gui = false; private final boolean gui = false;
@@ -54,16 +54,28 @@ public class PregenTask {
} }
public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) { public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) {
for (Position2 i : ORDERS.computeIfAbsent(pull, PregenTask::computeOrder)) { iterateRegion(xr, zr, s, pull.getX(), pull.getZ());
s.on(i.getX() + (xr << 5), i.getZ() + (zr << 5)); }
public static void iterateRegion(int xr, int zr, Spiraled s, int pullX, int pullZ) {
for (int packed : orderForPull(pullX, pullZ)) {
s.on(PowerOfTwoCoordinates.unpackLocal32X(packed) + PowerOfTwoCoordinates.regionToChunk(xr), PowerOfTwoCoordinates.unpackLocal32Z(packed) + PowerOfTwoCoordinates.regionToChunk(zr));
} }
} }
public static void iterateRegion(int xr, int zr, Spiraled s) { public static void iterateRegion(int xr, int zr, Spiraled s) {
iterateRegion(xr, zr, s, new Position2(-(xr << 5), -(zr << 5))); iterateRegion(xr, zr, s, -PowerOfTwoCoordinates.regionToChunk(xr), -PowerOfTwoCoordinates.regionToChunk(zr));
} }
private static KList<Position2> computeOrder(Position2 pull) { private static int[] orderForPull(int pullX, int pullZ) {
long key = orderKey(pullX, pullZ);
return ORDERS.computeIfAbsent(key, PregenTask::computeOrder);
}
private static int[] computeOrder(long key) {
int pullX = (int) (key >> 32);
int pullZ = (int) key;
Position2 pull = new Position2(pullX, pullZ);
KList<Position2> p = new KList<>(); KList<Position2> p = new KList<>();
new Spiraler(33, 33, (x, z) -> { new Spiraler(33, 33, (x, z) -> {
int xx = (x + 15); int xx = (x + 15);
@@ -76,18 +88,30 @@ public class PregenTask {
}).drain(); }).drain();
p.sort(Comparator.comparing((i) -> i.distance(pull))); p.sort(Comparator.comparing((i) -> i.distance(pull)));
return p; int[] packed = new int[p.size()];
for (int index = 0; index < p.size(); index++) {
Position2 position = p.get(index);
packed[index] = PowerOfTwoCoordinates.packLocal32(position.getX(), position.getZ());
}
return packed;
}
private static long orderKey(int pullX, int pullZ) {
long high = (long) pullX << 32;
long low = pullZ & 0xFFFFFFFFL;
return high | low;
} }
public void iterateRegions(Spiraled s) { public void iterateRegions(Spiraled s) {
var bound = bounds.region(); Bound bound = bounds.region();
new Spiraler(bound.sizeX, bound.sizeZ, ((x, z) -> { new Spiraler(bound.sizeX, bound.sizeZ, ((x, z) -> {
if (bound.check(x, z)) s.on(x, z); if (bound.check(x, z)) s.on(x, z);
})).setOffset(center.getX() >> 9, center.getZ() >> 9).drain(); })).setOffset(PowerOfTwoCoordinates.blockToRegionFloor(center.getX()), PowerOfTwoCoordinates.blockToRegionFloor(center.getZ())).drain();
} }
public void iterateChunks(int rX, int rZ, Spiraled s) { public void iterateChunks(int rX, int rZ, Spiraled s) {
var bound = bounds.chunk(); Bound bound = bounds.chunk();
iterateRegion(rX, rZ, ((x, z) -> { iterateRegion(rX, rZ, ((x, z) -> {
if (bound.check(x, z)) s.on(x, z); if (bound.check(x, z)) s.on(x, z);
})); }));
@@ -103,11 +127,11 @@ public class PregenTask {
} }
KList<RegionChunkCursor> cursors = new KList<>(); KList<RegionChunkCursor> cursors = new KList<>();
Bound bound = bounds.chunk();
iterateRegions((regionX, regionZ) -> { iterateRegions((regionX, regionZ) -> {
KList<Position2> chunks = new KList<>(); RegionChunkCursor cursor = new RegionChunkCursor(regionX, regionZ, bound);
iterateChunks(regionX, regionZ, (chunkX, chunkZ) -> chunks.add(new Position2(chunkX, chunkZ))); if (cursor.hasNext()) {
if (!chunks.isEmpty()) { cursors.add(cursor);
cursors.add(new RegionChunkCursor(regionX, regionZ, chunks));
} }
}); });
@@ -120,16 +144,18 @@ public class PregenTask {
} }
hasProgress = true; hasProgress = true;
Position2 chunk = cursor.next(); long chunk = cursor.next();
if (chunk == null) { if (chunk == Long.MIN_VALUE) {
continue; continue;
} }
int chunkX = (int) (chunk >> 32);
int chunkZ = (int) chunk;
boolean shouldContinue = spiraled.on( boolean shouldContinue = spiraled.on(
cursor.getRegionX(), cursor.getRegionX(),
cursor.getRegionZ(), cursor.getRegionZ(),
chunk.getX(), chunkX,
chunk.getZ(), chunkZ,
cursor.getIndex() == 1, cursor.getIndex() == 1,
!cursor.hasNext() !cursor.hasNext()
); );
@@ -155,8 +181,18 @@ public class PregenTask {
int minX = center.getX() - radiusX; int minX = center.getX() - radiusX;
int minZ = center.getZ() - radiusZ; int minZ = center.getZ() - radiusZ;
chunk = new Bound(minX >> 4, minZ >> 4, Math.ceilDiv(maxX, 16), Math.ceilDiv(maxZ, 16)); chunk = new Bound(
region = new Bound(minX >> 9, minZ >> 9, Math.ceilDiv(maxX, 512), Math.ceilDiv(maxZ, 512)); PowerOfTwoCoordinates.blockToChunkFloor(minX),
PowerOfTwoCoordinates.blockToChunkFloor(minZ),
PowerOfTwoCoordinates.ceilDivPow2(maxX, PowerOfTwoCoordinates.CHUNK_BITS),
PowerOfTwoCoordinates.ceilDivPow2(maxZ, PowerOfTwoCoordinates.CHUNK_BITS)
);
region = new Bound(
PowerOfTwoCoordinates.blockToRegionFloor(minX),
PowerOfTwoCoordinates.blockToRegionFloor(minZ),
PowerOfTwoCoordinates.ceilDivPow2(maxX, PowerOfTwoCoordinates.REGION_BITS),
PowerOfTwoCoordinates.ceilDivPow2(maxZ, PowerOfTwoCoordinates.REGION_BITS)
);
} }
public Bound chunk() { public Bound chunk() {
@@ -199,28 +235,60 @@ public class PregenTask {
private static final class RegionChunkCursor { private static final class RegionChunkCursor {
private final int regionX; private final int regionX;
private final int regionZ; private final int regionZ;
private final KList<Position2> chunks; private final Bound bound;
private int index; private final int[] order;
private final int chunkOffsetX;
private final int chunkOffsetZ;
private int scanIndex;
private int emittedIndex;
private int currentChunkX;
private int currentChunkZ;
private boolean hasCurrent;
private RegionChunkCursor(int regionX, int regionZ, KList<Position2> chunks) { private RegionChunkCursor(int regionX, int regionZ, Bound bound) {
this.regionX = regionX; this.regionX = regionX;
this.regionZ = regionZ; this.regionZ = regionZ;
this.chunks = chunks; this.bound = bound;
this.index = 0; this.chunkOffsetX = PowerOfTwoCoordinates.regionToChunk(regionX);
this.chunkOffsetZ = PowerOfTwoCoordinates.regionToChunk(regionZ);
this.order = orderForPull(-chunkOffsetX, -chunkOffsetZ);
this.scanIndex = 0;
this.emittedIndex = 0;
advance();
} }
private boolean hasNext() { private boolean hasNext() {
return index < chunks.size(); return hasCurrent;
} }
private Position2 next() { private long next() {
if (!hasNext()) { if (!hasNext()) {
return null; return Long.MIN_VALUE;
} }
Position2 value = chunks.get(index); long high = (long) currentChunkX << 32;
index++; long low = currentChunkZ & 0xFFFFFFFFL;
return value; emittedIndex++;
advance();
return high | low;
}
private void advance() {
hasCurrent = false;
while (scanIndex < order.length) {
int local = order[scanIndex];
scanIndex++;
int chunkX = chunkOffsetX + PowerOfTwoCoordinates.unpackLocal32X(local);
int chunkZ = chunkOffsetZ + PowerOfTwoCoordinates.unpackLocal32Z(local);
if (!bound.check(chunkX, chunkZ)) {
continue;
}
currentChunkX = chunkX;
currentChunkZ = chunkZ;
hasCurrent = true;
return;
}
} }
private int getRegionX() { private int getRegionX() {
@@ -232,7 +300,7 @@ public class PregenTask {
} }
private int getIndex() { private int getIndex() {
return index; return emittedIndex;
} }
} }
} }
@@ -5,6 +5,7 @@ import art.arcane.volmlib.util.data.Varint;
import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.documentation.RegionCoordinates; import art.arcane.volmlib.util.documentation.RegionCoordinates;
import art.arcane.volmlib.util.io.IO; import art.arcane.volmlib.util.io.IO;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockInputStream;
import net.jpountz.lz4.LZ4BlockOutputStream; import net.jpountz.lz4.LZ4BlockOutputStream;
@@ -39,19 +40,22 @@ public class PregenCacheImpl implements PregenCache {
@Override @Override
@ChunkCoordinates @ChunkCoordinates
public boolean isChunkCached(int x, int z) { public boolean isChunkCached(int x, int z) {
return getPlate(x >> 10, z >> 10).isCached( return getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 10), PowerOfTwoCoordinates.floorDivPow2(z, 10)).isCached(
(x >> 5) & 31, PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
(z >> 5) & 31, PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(z, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
region -> region.isCached(x & 31, z & 31) region -> region.isCached(
PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS)
)
); );
} }
@Override @Override
@RegionCoordinates @RegionCoordinates
public boolean isRegionCached(int x, int z) { public boolean isRegionCached(int x, int z) {
return getPlate(x >> 5, z >> 5).isCached( return getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.floorDivPow2(z, 5)).isCached(
x & 31, PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
z & 31, PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
Region::isCached Region::isCached
); );
} }
@@ -59,19 +63,22 @@ public class PregenCacheImpl implements PregenCache {
@Override @Override
@ChunkCoordinates @ChunkCoordinates
public void cacheChunk(int x, int z) { public void cacheChunk(int x, int z) {
getPlate(x >> 10, z >> 10).cache( getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 10), PowerOfTwoCoordinates.floorDivPow2(z, 10)).cache(
(x >> 5) & 31, PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
(z >> 5) & 31, PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(z, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
region -> region.cache(x & 31, z & 31) region -> region.cache(
PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS)
)
); );
} }
@Override @Override
@RegionCoordinates @RegionCoordinates
public void cacheRegion(int x, int z) { public void cacheRegion(int x, int z) {
getPlate(x >> 5, z >> 5).cache( getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.floorDivPow2(z, 5)).cache(
x & 31, PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
z & 31, PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
Region::cache Region::cache
); );
} }
@@ -215,7 +222,7 @@ public class PregenCacheImpl implements PregenCache {
return false; return false;
} }
int index = x * 32 + z; int index = PowerOfTwoCoordinates.packLocal32(x, z);
Region region = regions[index]; Region region = regions[index];
if (region == null) { if (region == null) {
region = new Region(); region = new Region();
@@ -240,7 +247,7 @@ public class PregenCacheImpl implements PregenCache {
return true; return true;
} }
Region region = regions[x * 32 + z]; Region region = regions[PowerOfTwoCoordinates.packLocal32(x, z)];
if (region == null) { if (region == null) {
return false; return false;
} }
@@ -294,7 +301,7 @@ public class PregenCacheImpl implements PregenCache {
return false; return false;
} }
int index = x * 32 + z; int index = PowerOfTwoCoordinates.packLocal32(x, z);
int wordIndex = index >> 6; int wordIndex = index >> 6;
long bit = 1L << (index & 63); long bit = 1L << (index & 63);
boolean current = (value[wordIndex] & bit) != 0L; boolean current = (value[wordIndex] & bit) != 0L;
@@ -317,7 +324,7 @@ public class PregenCacheImpl implements PregenCache {
} }
private boolean isCached(int x, int z) { private boolean isCached(int x, int z) {
int index = x * 32 + z; int index = PowerOfTwoCoordinates.packLocal32(x, z);
if (count == SIZE) { if (count == SIZE) {
return true; return true;
} }
@@ -35,14 +35,17 @@ import io.papermc.lib.PaperLib;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.World; import org.bukkit.World;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@@ -59,6 +62,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private final int effectiveWorkerThreads; private final int effectiveWorkerThreads;
private final int recommendedRuntimeConcurrencyCap; private final int recommendedRuntimeConcurrencyCap;
private final int configuredMaxConcurrency; private final int configuredMaxConcurrency;
private final Method directChunkAtAsyncUrgentMethod;
private final Method directChunkAtAsyncMethod;
private final String chunkAccessMode;
private final Executor executor; private final Executor executor;
private final Semaphore semaphore; private final Semaphore semaphore;
private final int threads; private final int threads;
@@ -90,10 +96,16 @@ public class AsyncPregenMethod implements PregeneratorMethod {
IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen(); IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen();
this.runtimeSchedulerMode = IrisRuntimeSchedulerMode.resolve(pregen); this.runtimeSchedulerMode = IrisRuntimeSchedulerMode.resolve(pregen);
this.foliaRuntime = runtimeSchedulerMode == IrisRuntimeSchedulerMode.FOLIA; this.foliaRuntime = runtimeSchedulerMode == IrisRuntimeSchedulerMode.FOLIA;
ChunkAsyncMethodSelection chunkAsyncMethodSelection = resolveChunkAsyncMethodSelection(world);
this.directChunkAtAsyncUrgentMethod = chunkAsyncMethodSelection.urgentMethod();
this.directChunkAtAsyncMethod = chunkAsyncMethodSelection.standardMethod();
this.chunkAccessMode = chunkAsyncMethodSelection.mode();
int detectedWorkerPoolThreads = resolveWorkerPoolThreads(); int detectedWorkerPoolThreads = resolveWorkerPoolThreads();
int detectedCpuThreads = Math.max(1, Runtime.getRuntime().availableProcessors()); int detectedCpuThreads = Math.max(1, Runtime.getRuntime().availableProcessors());
int configuredWorldGenThreads = Math.max(1, IrisSettings.get().getConcurrency().getWorldGenThreads()); int configuredWorldGenThreads = Math.max(1, IrisSettings.get().getConcurrency().getWorldGenThreads());
int workerThreadsForCap = Math.max(detectedCpuThreads, Math.max(configuredWorldGenThreads, Math.max(1, detectedWorkerPoolThreads))); int workerThreadsForCap = foliaRuntime
? resolveFoliaConcurrencyWorkerThreads(detectedWorkerPoolThreads, detectedCpuThreads, configuredWorldGenThreads)
: resolvePaperLikeConcurrencyWorkerThreads(detectedWorkerPoolThreads, detectedCpuThreads, configuredWorldGenThreads);
if (foliaRuntime) { if (foliaRuntime) {
this.paperLikeBackendMode = IrisPaperLikeBackendMode.AUTO; this.paperLikeBackendMode = IrisPaperLikeBackendMode.AUTO;
this.backendMode = "folia-region"; this.backendMode = "folia-region";
@@ -156,11 +168,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private void unloadAndSaveAllChunks() { private void unloadAndSaveAllChunks() {
if (foliaRuntime) { if (foliaRuntime) {
// Folia requires world/chunk mutations to be region-owned; periodic global unload/save is unsafe.
lastUse.clear(); lastUse.clear();
return; return;
} }
if (lastUse.isEmpty()) {
return;
}
try { try {
J.sfut(() -> { J.sfut(() -> {
if (world == null) { if (world == null) {
@@ -169,6 +184,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
} }
long minTime = M.ms() - 10_000; long minTime = M.ms() - 10_000;
AtomicBoolean unloaded = new AtomicBoolean(false);
lastUse.entrySet().removeIf(i -> { lastUse.entrySet().removeIf(i -> {
final Chunk chunk = i.getKey(); final Chunk chunk = i.getKey();
final Long lastUseTime = i.getValue(); final Long lastUseTime = i.getValue();
@@ -176,11 +192,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return true; return true;
if (lastUseTime < minTime) { if (lastUseTime < minTime) {
chunk.unload(); chunk.unload();
unloaded.set(true);
return true; return true;
} }
return false; return false;
}); });
world.save(); if (unloaded.get()) {
world.save();
}
}).get(); }).get();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
@@ -294,6 +313,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return recommendedCap; return recommendedCap;
} }
static int resolvePaperLikeConcurrencyWorkerThreads(int detectedWorkerPoolThreads, int detectedCpuThreads, int configuredWorldGenThreads) {
if (detectedWorkerPoolThreads > 0) {
return detectedWorkerPoolThreads;
}
return Math.max(1, Math.max(detectedCpuThreads, configuredWorldGenThreads));
}
static int computeFoliaRecommendedCap(int workerThreads) { static int computeFoliaRecommendedCap(int workerThreads) {
int normalizedWorkers = Math.max(1, workerThreads); int normalizedWorkers = Math.max(1, workerThreads);
int recommendedCap = normalizedWorkers * 4; int recommendedCap = normalizedWorkers * 4;
@@ -308,6 +335,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return recommendedCap; return recommendedCap;
} }
static int resolveFoliaConcurrencyWorkerThreads(int detectedWorkerPoolThreads, int detectedCpuThreads, int configuredWorldGenThreads) {
return Math.max(detectedCpuThreads, Math.max(configuredWorldGenThreads, Math.max(1, detectedWorkerPoolThreads)));
}
static int applyRuntimeConcurrencyCap(int maxConcurrency, boolean foliaRuntime, int workerThreads) { static int applyRuntimeConcurrencyCap(int maxConcurrency, boolean foliaRuntime, int workerThreads) {
int normalizedMaxConcurrency = Math.max(1, maxConcurrency); int normalizedMaxConcurrency = Math.max(1, maxConcurrency);
int recommendedCap = foliaRuntime int recommendedCap = foliaRuntime
@@ -407,6 +438,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
Iris.info("Async pregen init: world=" + world.getName() Iris.info("Async pregen init: world=" + world.getName()
+ ", mode=" + runtimeSchedulerMode.name().toLowerCase(Locale.ROOT) + ", mode=" + runtimeSchedulerMode.name().toLowerCase(Locale.ROOT)
+ ", backend=" + backendMode + ", backend=" + backendMode
+ ", chunkAccess=" + chunkAccessMode
+ ", threads=" + threads + ", threads=" + threads
+ ", adaptiveLimit=" + adaptiveInFlightLimit.get() + ", adaptiveLimit=" + adaptiveInFlightLimit.get()
+ ", workerPoolThreads=" + workerPoolThreads + ", workerPoolThreads=" + workerPoolThreads
@@ -487,6 +519,96 @@ public class AsyncPregenMethod implements PregeneratorMethod {
executor.generate(x, z, listener); executor.generate(x, z, listener);
} }
private CompletableFuture<Chunk> requestChunkAsync(int x, int z) {
Throwable failure = null;
if (directChunkAtAsyncUrgentMethod != null) {
try {
return invokeChunkFuture(directChunkAtAsyncUrgentMethod, x, z, true, urgent);
} catch (Throwable e) {
failure = e;
}
}
if (directChunkAtAsyncMethod != null) {
try {
return invokeChunkFuture(directChunkAtAsyncMethod, x, z, true, urgent);
} catch (Throwable e) {
if (failure == null) {
failure = e;
}
}
}
try {
CompletableFuture<Chunk> future = PaperLib.getChunkAtAsync(world, x, z, true, urgent);
if (future != null) {
return future;
}
} catch (Throwable e) {
if (failure == null) {
failure = e;
}
}
if (failure == null) {
failure = new IllegalStateException("Chunk async access returned no future.");
}
return CompletableFuture.failedFuture(new IllegalStateException("Failed to request async chunk " + x + "," + z + " in world " + world.getName(), failure));
}
@SuppressWarnings("unchecked")
private CompletableFuture<Chunk> invokeChunkFuture(Method method, int x, int z, boolean generate, boolean urgentRequest) throws Throwable {
Object result;
try {
if (method.getParameterCount() == 4) {
result = method.invoke(world, x, z, generate, urgentRequest);
} else {
result = method.invoke(world, x, z, generate);
}
} catch (InvocationTargetException e) {
throw e.getCause() == null ? e : e.getCause();
}
if (result instanceof CompletableFuture<?>) {
return (CompletableFuture<Chunk>) result;
}
throw new IllegalStateException("Chunk async method returned a non-future result.");
}
private static ChunkAsyncMethodSelection resolveChunkAsyncMethodSelection(World world) {
if (world == null) {
return new ChunkAsyncMethodSelection(null, null, "paperlib");
}
Class<?> worldClass = world.getClass();
Method urgentMethod = resolveChunkAsyncMethod(worldClass, int.class, int.class, boolean.class, boolean.class);
Method standardMethod = resolveChunkAsyncMethod(worldClass, int.class, int.class, boolean.class);
if (urgentMethod != null) {
return new ChunkAsyncMethodSelection(urgentMethod, standardMethod, "world#getChunkAtAsync(int,int,boolean,boolean)");
}
if (standardMethod != null) {
return new ChunkAsyncMethodSelection(null, standardMethod, "world#getChunkAtAsync(int,int,boolean)");
}
return new ChunkAsyncMethodSelection(null, null, "paperlib");
}
private static Method resolveChunkAsyncMethod(Class<?> worldClass, Class<?>... parameterTypes) {
try {
return worldClass.getMethod("getChunkAtAsync", parameterTypes);
} catch (NoSuchMethodException ignored) {
}
try {
return World.class.getMethod("getChunkAtAsync", parameterTypes);
} catch (NoSuchMethodException ignored) {
}
return null;
}
@Override @Override
public Mantle getMantle() { public Mantle getMantle() {
if (IrisToolbelt.isIrisWorld(world)) { if (IrisToolbelt.isIrisWorld(world)) {
@@ -547,14 +669,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
@Override @Override
public void generate(int x, int z, PregenListener listener) { public void generate(int x, int z, PregenListener listener) {
try { try {
PaperLib.getChunkAtAsync(world, x, z, true, urgent) requestChunkAsync(x, z)
.orTimeout(timeoutSeconds, TimeUnit.SECONDS) .orTimeout(timeoutSeconds, TimeUnit.SECONDS)
.whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable)); .whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable));
return; return;
} catch (Throwable ignored) { } catch (Throwable ignored) {
} }
if (!J.runRegion(world, x, z, () -> PaperLib.getChunkAtAsync(world, x, z, true, urgent) if (!J.runRegion(world, x, z, () -> requestChunkAsync(x, z)
.orTimeout(timeoutSeconds, TimeUnit.SECONDS) .orTimeout(timeoutSeconds, TimeUnit.SECONDS)
.whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable)))) { .whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable)))) {
markFinished(false); markFinished(false);
@@ -598,7 +720,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
service.submit(() -> { service.submit(() -> {
boolean success = false; boolean success = false;
try { try {
Chunk i = PaperLib.getChunkAtAsync(world, x, z, true, urgent) Chunk i = requestChunkAsync(x, z)
.orTimeout(timeoutSeconds, TimeUnit.SECONDS) .orTimeout(timeoutSeconds, TimeUnit.SECONDS)
.exceptionally(e -> onChunkFutureFailure(x, z, e)) .exceptionally(e -> onChunkFutureFailure(x, z, e))
.get(); .get();
@@ -632,7 +754,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private class TicketExecutor implements Executor { private class TicketExecutor implements Executor {
@Override @Override
public void generate(int x, int z, PregenListener listener) { public void generate(int x, int z, PregenListener listener) {
PaperLib.getChunkAtAsync(world, x, z, true, urgent) requestChunkAsync(x, z)
.orTimeout(timeoutSeconds, TimeUnit.SECONDS) .orTimeout(timeoutSeconds, TimeUnit.SECONDS)
.exceptionally(e -> onChunkFutureFailure(x, z, e)) .exceptionally(e -> onChunkFutureFailure(x, z, e))
.thenAccept(i -> { .thenAccept(i -> {
@@ -653,4 +775,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
}); });
} }
} }
private record ChunkAsyncMethodSelection(Method urgentMethod, Method standardMethod, String mode) {
}
} }
@@ -14,8 +14,10 @@ import art.arcane.volmlib.util.math.RNG;
import art.arcane.iris.util.common.plugin.IrisService; import art.arcane.iris.util.common.plugin.IrisService;
import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.iris.util.common.plugin.VolmitSender;
import art.arcane.volmlib.util.scheduling.Looper; import art.arcane.volmlib.util.scheduling.Looper;
import art.arcane.iris.util.project.stream.utility.CachedDoubleStream2D;
import art.arcane.iris.util.project.stream.utility.CachedStream2D; import art.arcane.iris.util.project.stream.utility.CachedStream2D;
import art.arcane.iris.util.project.stream.utility.CachedStream3D; import art.arcane.iris.util.project.stream.utility.CachedStream3D;
import art.arcane.iris.core.gui.PregeneratorJob;
import lombok.Synchronized; import lombok.Synchronized;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
@@ -98,7 +100,7 @@ public class IrisEngineSVC implements IrisService {
double total = 0D; double total = 0D;
int count = 0; int count = 0;
for (var cache : preservation.getCaches()) { for (var cache : preservation.getCaches()) {
if (!(cache instanceof CachedStream2D<?>)) { if (!(cache instanceof CachedStream2D<?>) && !(cache instanceof CachedDoubleStream2D)) {
continue; continue;
} }
@@ -126,6 +128,7 @@ public class IrisEngineSVC implements IrisService {
var type = switch (cache) { var type = switch (cache) {
case ResourceLoader<?> ignored -> 0; case ResourceLoader<?> ignored -> 0;
case CachedStream2D<?> ignored -> 1; case CachedStream2D<?> ignored -> 1;
case CachedDoubleStream2D ignored -> 1;
case CachedStream3D<?> ignored -> 2; case CachedStream3D<?> ignored -> 2;
default -> 3; default -> 3;
}; };
@@ -250,6 +253,10 @@ public class IrisEngineSVC implements IrisService {
return false; return false;
} }
static boolean shouldSkipMantleReductionForMaintenance(boolean maintenanceActive, boolean pregeneratorTargetsWorld) {
return maintenanceActive && !pregeneratorTargetsWorld;
}
private final class Registered { private final class Registered {
private final String name; private final String name;
private final PlatformChunkGenerator access; private final PlatformChunkGenerator access;
@@ -286,7 +293,7 @@ public class IrisEngineSVC implements IrisService {
|| !shouldReduce(engine)) || !shouldReduce(engine))
return; return;
World engineWorld = engine.getWorld().realWorld(); World engineWorld = engine.getWorld().realWorld();
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) { if (shouldSkipForMaintenance(engineWorld)) {
return; return;
} }
@@ -313,7 +320,7 @@ public class IrisEngineSVC implements IrisService {
|| !shouldReduce(engine)) || !shouldReduce(engine))
return; return;
World engineWorld = engine.getWorld().realWorld(); World engineWorld = engine.getWorld().realWorld();
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) { if (shouldSkipForMaintenance(engineWorld)) {
return; return;
} }
@@ -363,7 +370,32 @@ public class IrisEngineSVC implements IrisService {
} }
private boolean shouldReduce(Engine engine) { private boolean shouldReduce(Engine engine) {
return !engine.isStudio() || IrisSettings.get().getPerformance().isTrimMantleInStudio(); if (!engine.isStudio() || IrisSettings.get().getPerformance().isTrimMantleInStudio()) {
return true;
}
World world = engine.getWorld().realWorld();
if (world == null) {
return false;
}
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
return pregeneratorJob != null && pregeneratorJob.targetsWorld(world);
}
private boolean shouldSkipForMaintenance(@Nullable World world) {
if (world == null) {
return false;
}
boolean maintenanceActive = IrisToolbelt.isWorldMaintenanceActive(world);
if (!maintenanceActive) {
return false;
}
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
boolean pregeneratorTargetsWorld = pregeneratorJob != null && pregeneratorJob.targetsWorld(world);
return shouldSkipMantleReductionForMaintenance(maintenanceActive, pregeneratorTargetsWorld);
} }
} }
} }
@@ -200,11 +200,11 @@ public class IrisComplex implements DataProvider {
heightStream = ProceduralStream.of((x, z) -> { heightStream = ProceduralStream.of((x, z) -> {
IrisBiome b = focusBiome != null ? focusBiome : baseBiomeStream.get(x, z); IrisBiome b = focusBiome != null ? focusBiome : baseBiomeStream.get(x, z);
return getHeight(engine, b, x, z, engine.getSeedManager().getHeight()); return getHeight(engine, b, x, z, engine.getSeedManager().getHeight());
}, Interpolated.DOUBLE).cache2D("heightStream", engine, cacheSize).waste("Height Stream"); }, Interpolated.DOUBLE).cache2DDouble("heightStream", engine, cacheSize).waste("Height Stream");
roundedHeighteightStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z)) roundedHeighteightStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z))
.round().waste("Rounded Height Stream"); .round().waste("Rounded Height Stream");
slopeStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z)) slopeStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z))
.slope(3).cache2D("slopeStream", engine, cacheSize).waste("Slope Stream"); .slope(3).cache2DDouble("slopeStream", engine, cacheSize).waste("Slope Stream");
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D, trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
b -> focusBiome)) b -> focusBiome))
.cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream .cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream
@@ -215,7 +215,7 @@ public class IrisComplex implements DataProvider {
trueBiomeDerivativeStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z)) trueBiomeDerivativeStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
.convert(IrisBiome::getDerivative).cache2D("trueBiomeDerivativeStream", engine, cacheSize).waste("True Biome Derivative Stream"); .convert(IrisBiome::getDerivative).cache2D("trueBiomeDerivativeStream", engine, cacheSize).waste("True Biome Derivative Stream");
heightFluidStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z)) heightFluidStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z))
.max(fluidHeight).cache2D("heightFluidStream", engine, cacheSize).waste("Height Fluid Stream"); .max(fluidHeight).cache2DDouble("heightFluidStream", engine, cacheSize).waste("Height Fluid Stream");
maxHeightStream = ProceduralStream.ofDouble((x, z) -> height).waste("Max Height Stream"); maxHeightStream = ProceduralStream.ofDouble((x, z) -> height).waste("Max Height Stream");
terrainSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z)) terrainSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainSurfaceDecoration", engine, cacheSize).waste("Surface Decoration Stream"); .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainSurfaceDecoration", engine, cacheSize).waste("Surface Decoration Stream");
@@ -37,6 +37,7 @@ import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk; import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.mantle.flag.MantleFlag; import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
import art.arcane.volmlib.util.math.Position2; import art.arcane.volmlib.util.math.Position2;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.Matter;
@@ -219,8 +220,8 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
} }
J.runEntity(player, () -> { J.runEntity(player, () -> {
int centerX = player.getLocation().getBlockX() >> 4; int centerX = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockX());
int centerZ = player.getLocation().getBlockZ() >> 4; int centerZ = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockZ());
int radius = 1; int radius = 1;
for (int x = -radius; x <= radius; x++) { for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) { for (int z = -radius; z <= radius; z++) {
@@ -278,8 +279,8 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
} }
J.runEntity(player, () -> { J.runEntity(player, () -> {
int centerX = player.getLocation().getBlockX() >> 4; int centerX = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockX());
int centerZ = player.getLocation().getBlockZ() >> 4; int centerZ = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockZ());
int radius = 1; int radius = 1;
for (int x = -radius; x <= radius; x++) { for (int x = -radius; x <= radius; x++) {
@@ -632,12 +633,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
.filter((i) -> i.isValid(biome)), .filter((i) -> i.isValid(biome)),
Stream.concat(getData() Stream.concat(getData()
.getSpawnerLoader() .getSpawnerLoader()
.loadAll(getEngine().getRegion(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) .loadAll(getEngine().getRegion(PowerOfTwoCoordinates.chunkToBlock(c.getX()), PowerOfTwoCoordinates.chunkToBlock(c.getZ())).getEntitySpawners())
.shuffleCopy(RNG.r) .shuffleCopy(RNG.r)
.stream() .stream()
.filter(filter), .filter(filter),
getData().getSpawnerLoader() getData().getSpawnerLoader()
.loadAll(getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) .loadAll(getEngine().getSurfaceBiome(PowerOfTwoCoordinates.chunkToBlock(c.getX()), PowerOfTwoCoordinates.chunkToBlock(c.getZ())).getEntitySpawners())
.shuffleCopy(RNG.r) .shuffleCopy(RNG.r)
.stream() .stream()
.filter(filter))) .filter(filter)))
@@ -668,13 +669,13 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
private void spawn(IrisPosition pos, IrisEntitySpawn i) { private void spawn(IrisPosition pos, IrisEntitySpawn i) {
IrisSpawner ref = i.getReferenceSpawner(); IrisSpawner ref = i.getReferenceSpawner();
if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4)) if (!ref.canSpawn(getEngine(), PowerOfTwoCoordinates.blockToChunkFloor(pos.getX()), PowerOfTwoCoordinates.blockToChunkFloor(pos.getZ())))
return; return;
int s = i.spawn(getEngine(), pos, RNG.r); int s = i.spawn(getEngine(), pos, RNG.r);
actuallySpawned += s; actuallySpawned += s;
if (s > 0) { if (s > 0) {
ref.spawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4); ref.spawn(getEngine(), PowerOfTwoCoordinates.blockToChunkFloor(pos.getX()), PowerOfTwoCoordinates.blockToChunkFloor(pos.getZ()));
energy -= s * ((i.getEnergyMultiplier() * ref.getEnergyMultiplier() * 1)); energy -= s * ((i.getEnergyMultiplier() * ref.getEnergyMultiplier() * 1));
} }
} }
@@ -26,6 +26,7 @@ import art.arcane.iris.engine.object.IrisCaveProfile;
import art.arcane.iris.engine.object.IrisRange; import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.util.project.noise.CNG; import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk; import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern; import art.arcane.volmlib.util.matter.MatterCavern;
@@ -55,8 +56,11 @@ public class IrisCaveCarver3D {
private final MatterCavern carveAir; private final MatterCavern carveAir;
private final MatterCavern carveLava; private final MatterCavern carveLava;
private final MatterCavern carveForcedAir; private final MatterCavern carveForcedAir;
private final double normalizationFactor;
private final double baseWeight; private final double baseWeight;
private final double detailWeight; private final double detailWeight;
private final double detailMinContribution;
private final double detailMaxContribution;
private final double warpStrength; private final double warpStrength;
private final boolean hasWarp; private final boolean hasWarp;
private final boolean hasModules; private final boolean hasModules;
@@ -93,8 +97,11 @@ public class IrisCaveCarver3D {
this.modules = moduleStates.toArray(new ModuleState[0]); this.modules = moduleStates.toArray(new ModuleState[0]);
double normalization = weight <= 0 ? 1 : weight; double normalization = weight <= 0 ? 1 : weight;
normalizationFactor = normalization;
inverseNormalization = 1D / normalization; inverseNormalization = 1D / normalization;
hasModules = modules.length > 0; hasModules = modules.length > 0;
detailMinContribution = -detailWeight;
detailMaxContribution = detailWeight;
} }
public int carve(MantleWriter writer, int chunkX, int chunkZ) { public int carve(MantleWriter writer, int chunkX, int chunkZ) {
@@ -178,8 +185,8 @@ public class IrisCaveCarver3D {
return 0; return 0;
} }
int x0 = chunkX << 4; int x0 = PowerOfTwoCoordinates.chunkToBlock(chunkX);
int z0 = chunkZ << 4; int z0 = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
int[] columnMaxY = scratch.columnMaxY; int[] columnMaxY = scratch.columnMaxY;
int[] surfaceBreakFloorY = scratch.surfaceBreakFloorY; int[] surfaceBreakFloorY = scratch.surfaceBreakFloorY;
boolean[] surfaceBreakColumn = scratch.surfaceBreakColumn; boolean[] surfaceBreakColumn = scratch.surfaceBreakColumn;
@@ -193,7 +200,7 @@ public class IrisCaveCarver3D {
int x = x0 + lx; int x = x0 + lx;
for (int lz = 0; lz < 16; lz++) { for (int lz = 0; lz < 16; lz++) {
int z = z0 + lz; int z = z0 + lz;
int index = (lx << 4) | lz; int index = PowerOfTwoCoordinates.packLocal16(lx, lz);
int columnSurfaceY; int columnSurfaceY;
if (precomputedSurfaceHeights != null && precomputedSurfaceHeights.length > index) { if (precomputedSurfaceHeights != null && precomputedSurfaceHeights.length > index) {
columnSurfaceY = precomputedSurfaceHeights[index]; columnSurfaceY = precomputedSurfaceHeights[index];
@@ -202,7 +209,7 @@ public class IrisCaveCarver3D {
} }
int clearanceTopY = Math.min(maxY, Math.max(minY, columnSurfaceY - surfaceClearance)); int clearanceTopY = Math.min(maxY, Math.max(minY, columnSurfaceY - surfaceClearance));
boolean breakColumn = allowSurfaceBreak boolean breakColumn = allowSurfaceBreak
&& signed(surfaceBreakDensity.noiseFast2D(x, z)) >= surfaceBreakNoiseThreshold; && surfaceBreakDensity.noiseFastSigned2D(x, z) >= surfaceBreakNoiseThreshold;
int columnTopY = breakColumn int columnTopY = breakColumn
? Math.min(maxY, Math.max(minY, columnSurfaceY)) ? Math.min(maxY, Math.max(minY, columnSurfaceY))
: clearanceTopY; : clearanceTopY;
@@ -399,13 +406,14 @@ public class IrisCaveCarver3D {
} }
int[] planeColumnIndices = scratch.planeColumnIndices; int[] planeColumnIndices = scratch.planeColumnIndices;
double[] planeDensity = scratch.planeDensity; double[] planeThresholdLimit = scratch.planeThresholdLimit;
int minSection = minY >> 4; boolean[] planeCarve = scratch.planeCarve;
int maxSection = maxY >> 4; int minSection = PowerOfTwoCoordinates.floorDivPow2(minY, 4);
int maxSection = PowerOfTwoCoordinates.floorDivPow2(maxY, 4);
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) { for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, sectionIndex << 4); int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
int sectionMaxY = Math.min(maxY, (sectionIndex << 4) + 15); int sectionMaxY = Math.min(maxY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex) + 15);
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, sectionIndex); MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, sectionIndex);
for (int y = sectionMinY; y <= sectionMaxY; y++) { for (int y = sectionMinY; y <= sectionMaxY; y++) {
@@ -415,7 +423,14 @@ public class IrisCaveCarver3D {
continue; continue;
} }
planeColumnIndices[planeCount] = activeColumnIndices[activeIndex]; int columnIndex = activeColumnIndices[activeIndex];
planeColumnIndices[planeCount] = columnIndex;
double localThreshold = passThreshold[columnIndex];
if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) {
localThreshold += surfaceBreakThresholdBoost;
}
localThreshold -= verticalEdgeFade[y - minY];
planeThresholdLimit[planeCount] = localThreshold * normalizationFactor;
planeCount++; planeCount++;
} }
@@ -423,24 +438,19 @@ public class IrisCaveCarver3D {
continue; continue;
} }
fillDensityPlane(x0, z0, y, planeColumnIndices, planeCount, planeDensity); classifyDensityPlane(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
int fadeIndex = y - minY; int fadeIndex = y - minY;
int localY = y & 15; int localY = y & 15;
MatterCavern matter = matterByY[fadeIndex]; MatterCavern matter = matterByY[fadeIndex];
if (skipExistingCarved) { if (skipExistingCarved) {
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
int columnIndex = planeColumnIndices[planeIndex]; if (!planeCarve[planeIndex]) {
double localThreshold = passThreshold[columnIndex];
if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) {
localThreshold += surfaceBreakThresholdBoost;
}
localThreshold -= verticalEdgeFade[fadeIndex];
if (planeDensity[planeIndex] > localThreshold) {
continue; continue;
} }
int localX = columnIndex >> 4; int columnIndex = planeColumnIndices[planeIndex];
int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
int localZ = columnIndex & 15; int localZ = columnIndex & 15;
if (cavernSlice.get(localX, localY, localZ) != null) { if (cavernSlice.get(localX, localY, localZ) != null) {
continue; continue;
@@ -453,17 +463,12 @@ public class IrisCaveCarver3D {
} }
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
int columnIndex = planeColumnIndices[planeIndex]; if (!planeCarve[planeIndex]) {
double localThreshold = passThreshold[columnIndex];
if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) {
localThreshold += surfaceBreakThresholdBoost;
}
localThreshold -= verticalEdgeFade[fadeIndex];
if (planeDensity[planeIndex] > localThreshold) {
continue; continue;
} }
int localX = columnIndex >> 4; int columnIndex = planeColumnIndices[planeIndex];
int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
int localZ = columnIndex & 15; int localZ = columnIndex & 15;
cavernSlice.set(localX, localY, localZ, matter); cavernSlice.set(localX, localY, localZ, matter);
carved++; carved++;
@@ -519,7 +524,7 @@ public class IrisCaveCarver3D {
int lz1 = lz + 1; int lz1 = lz + 1;
int activeColumns = 0; int activeColumns = 0;
int index00 = (lx << 4) | lz; int index00 = PowerOfTwoCoordinates.packLocal16(lx, lz);
if (!Double.isNaN(passThreshold[index00])) { if (!Double.isNaN(passThreshold[index00])) {
tileIndices[activeColumns] = index00; tileIndices[activeColumns] = index00;
tileLocalX[activeColumns] = lx; tileLocalX[activeColumns] = lx;
@@ -528,7 +533,7 @@ public class IrisCaveCarver3D {
activeColumns++; activeColumns++;
} }
int index01 = (lx << 4) | lz1; int index01 = PowerOfTwoCoordinates.packLocal16(lx, lz1);
if (!Double.isNaN(passThreshold[index01])) { if (!Double.isNaN(passThreshold[index01])) {
tileIndices[activeColumns] = index01; tileIndices[activeColumns] = index01;
tileLocalX[activeColumns] = lx; tileLocalX[activeColumns] = lx;
@@ -537,7 +542,7 @@ public class IrisCaveCarver3D {
activeColumns++; activeColumns++;
} }
int index10 = (lx1 << 4) | lz; int index10 = PowerOfTwoCoordinates.packLocal16(lx1, lz);
if (!Double.isNaN(passThreshold[index10])) { if (!Double.isNaN(passThreshold[index10])) {
tileIndices[activeColumns] = index10; tileIndices[activeColumns] = index10;
tileLocalX[activeColumns] = lx1; tileLocalX[activeColumns] = lx1;
@@ -546,7 +551,7 @@ public class IrisCaveCarver3D {
activeColumns++; activeColumns++;
} }
int index11 = (lx1 << 4) | lz1; int index11 = PowerOfTwoCoordinates.packLocal16(lx1, lz1);
if (!Double.isNaN(passThreshold[index11])) { if (!Double.isNaN(passThreshold[index11])) {
tileIndices[activeColumns] = index11; tileIndices[activeColumns] = index11;
tileLocalX[activeColumns] = lx1; tileLocalX[activeColumns] = lx1;
@@ -574,7 +579,7 @@ public class IrisCaveCarver3D {
int stampMaxY = Math.min(maxY, y + 1); int stampMaxY = Math.min(maxY, y + 1);
for (int yy = y; yy <= stampMaxY; yy++) { for (int yy = y; yy <= stampMaxY; yy++) {
MatterCavern matter = matterByY[yy - minY]; MatterCavern matter = matterByY[yy - minY];
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, yy >> 4); MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, PowerOfTwoCoordinates.floorDivPow2(yy, 4));
int localY = yy & 15; int localY = yy & 15;
int fadeIndex = yy - minY; int fadeIndex = yy - minY;
for (int columnIndex = 0; columnIndex < activeColumns; columnIndex++) { for (int columnIndex = 0; columnIndex < activeColumns; columnIndex++) {
@@ -640,7 +645,7 @@ public class IrisCaveCarver3D {
int x = x0 + lx; int x = x0 + lx;
for (int lz = 0; lz < 16; lz++) { for (int lz = 0; lz < 16; lz++) {
int z = z0 + lz; int z = z0 + lz;
int index = (lx << 4) | lz; int index = PowerOfTwoCoordinates.packLocal16(lx, lz);
double columnWeight = clampedWeights[index]; double columnWeight = clampedWeights[index];
if (columnWeight <= minWeight) { if (columnWeight <= minWeight) {
continue; continue;
@@ -669,7 +674,7 @@ public class IrisCaveCarver3D {
int carveMaxY = Math.min(columnTopY, y + sampleStep - 1); int carveMaxY = Math.min(columnTopY, y + sampleStep - 1);
for (int yy = y; yy <= carveMaxY; yy++) { for (int yy = y; yy <= carveMaxY; yy++) {
MatterCavern matter = matterByY[yy - minY]; MatterCavern matter = matterByY[yy - minY];
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, yy >> 4); MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, PowerOfTwoCoordinates.floorDivPow2(yy, 4));
int localY = yy & 15; int localY = yy & 15;
if (skipExistingCarved) { if (skipExistingCarved) {
if (cavernSlice.get(lx, localY, lz) == null) { if (cavernSlice.get(lx, localY, lz) == null) {
@@ -719,153 +724,234 @@ public class IrisCaveCarver3D {
return sampleDensityWarpModules(x, y, z); return sampleDensityWarpModules(x, y, z);
} }
private void fillDensityPlane(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) { private void classifyDensityPlane(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
if (!hasWarp) { if (!hasWarp) {
if (!hasModules) { if (!hasModules) {
fillDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity); classifyDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
return; return;
} }
fillDensityPlaneNoWarpModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity); classifyDensityPlaneNoWarpModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
return; return;
} }
if (!hasModules) { if (!hasModules) {
fillDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeCount, planeDensity); classifyDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
return; return;
} }
fillDensityPlaneWarpModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity); classifyDensityPlaneWarpModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
} }
private void fillDensityPlaneNoWarpNoModules(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) { private void classifyDensityPlaneNoWarpNoModules(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
CNG localBaseDensity = baseDensity; CNG localBaseDensity = baseDensity;
CNG localDetailDensity = detailDensity; CNG localDetailDensity = detailDensity;
double localBaseWeight = baseWeight; double localBaseWeight = baseWeight;
double localDetailWeight = detailWeight; double localDetailWeight = detailWeight;
double normalization = inverseNormalization; double detailMin = detailMinContribution;
double detailMax = detailMaxContribution;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
int columnIndex = planeColumnIndices[planeIndex]; int columnIndex = planeColumnIndices[planeIndex];
int x = x0 + (columnIndex >> 4); int x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
int z = z0 + (columnIndex & 15); int z = z0 + (columnIndex & 15);
double density = signed(localBaseDensity.noiseFast3D(x, y, z)) * localBaseWeight; double thresholdLimit = planeThresholdLimit[planeIndex];
density += signed(localDetailDensity.noiseFast3D(x, y, z)) * localDetailWeight; double density = localBaseDensity.noiseFastSigned3D(x, y, z) * localBaseWeight;
planeDensity[planeIndex] = density * normalization; if ((density + detailMin) > thresholdLimit) {
} planeCarve[planeIndex] = false;
} continue;
}
private void fillDensityPlaneNoWarpModules(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) { if ((density + detailMax) <= thresholdLimit) {
CNG localBaseDensity = baseDensity; planeCarve[planeIndex] = true;
CNG localDetailDensity = detailDensity; continue;
ModuleState[] localModules = modules;
double localBaseWeight = baseWeight;
double localDetailWeight = detailWeight;
double normalization = inverseNormalization;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
int columnIndex = planeColumnIndices[planeIndex];
int x = x0 + (columnIndex >> 4);
int z = z0 + (columnIndex & 15);
double density = signed(localBaseDensity.noiseFast3D(x, y, z)) * localBaseWeight;
density += signed(localDetailDensity.noiseFast3D(x, y, z)) * localDetailWeight;
for (int moduleIndex = 0; moduleIndex < localModules.length; moduleIndex++) {
ModuleState module = localModules[moduleIndex];
if (y < module.minY || y > module.maxY) {
continue;
}
double moduleDensity = signed(module.density.noiseFast3D(x, y, z)) - module.threshold;
if (module.invert) {
moduleDensity = -moduleDensity;
}
density += moduleDensity * module.weight;
} }
planeDensity[planeIndex] = density * normalization; density += localDetailDensity.noiseFastSigned3D(x, y, z) * localDetailWeight;
planeCarve[planeIndex] = density <= thresholdLimit;
} }
} }
private void fillDensityPlaneWarpOnly(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) { private void classifyDensityPlaneNoWarpModules(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
CNG localBaseDensity = baseDensity;
CNG localDetailDensity = detailDensity;
Scratch scratch = SCRATCH.get();
int activeModuleCount = prepareActiveModules(scratch, y);
if (activeModuleCount == 0) {
classifyDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
return;
}
ModuleState[] localModules = scratch.activeModules;
double[] remainingMin = scratch.activeModuleRemainingMin;
double[] remainingMax = scratch.activeModuleRemainingMax;
double localBaseWeight = baseWeight;
double localDetailWeight = detailWeight;
double detailMin = detailMinContribution;
double detailMax = detailMaxContribution;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
int columnIndex = planeColumnIndices[planeIndex];
int x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
int z = z0 + (columnIndex & 15);
double thresholdLimit = planeThresholdLimit[planeIndex];
double density = localBaseDensity.noiseFastSigned3D(x, y, z) * localBaseWeight;
if ((density + detailMin + remainingMin[0]) > thresholdLimit) {
planeCarve[planeIndex] = false;
continue;
}
if ((density + detailMax + remainingMax[0]) <= thresholdLimit) {
planeCarve[planeIndex] = true;
continue;
}
density += localDetailDensity.noiseFastSigned3D(x, y, z) * localDetailWeight;
if ((density + remainingMin[0]) > thresholdLimit) {
planeCarve[planeIndex] = false;
continue;
}
if ((density + remainingMax[0]) <= thresholdLimit) {
planeCarve[planeIndex] = true;
continue;
}
for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
ModuleState module = localModules[moduleIndex];
density += module.sample(x, y, z);
if ((density + remainingMin[moduleIndex + 1]) > thresholdLimit) {
density = Double.POSITIVE_INFINITY;
break;
}
if ((density + remainingMax[moduleIndex + 1]) <= thresholdLimit) {
density = Double.NEGATIVE_INFINITY;
break;
}
}
planeCarve[planeIndex] = density <= thresholdLimit;
}
}
private void classifyDensityPlaneWarpOnly(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
CNG localBaseDensity = baseDensity; CNG localBaseDensity = baseDensity;
CNG localDetailDensity = detailDensity; CNG localDetailDensity = detailDensity;
CNG localWarpDensity = warpDensity; CNG localWarpDensity = warpDensity;
double localBaseWeight = baseWeight; double localBaseWeight = baseWeight;
double localDetailWeight = detailWeight; double localDetailWeight = detailWeight;
double localWarpStrength = warpStrength; double localWarpStrength = warpStrength;
double normalization = inverseNormalization; double detailMin = detailMinContribution;
double detailMax = detailMaxContribution;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
int columnIndex = planeColumnIndices[planeIndex]; int columnIndex = planeColumnIndices[planeIndex];
double x = x0 + (columnIndex >> 4); double x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
double z = z0 + (columnIndex & 15); double z = z0 + (columnIndex & 15);
double warpA = signed(localWarpDensity.noiseFast3D(x, y, z)); double thresholdLimit = planeThresholdLimit[planeIndex];
double warpB = signed(localWarpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); double warpA = localWarpDensity.noiseFastSigned3D(x, y, z);
double warpB = localWarpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
double warpedX = x + (warpA * localWarpStrength); double warpedX = x + (warpA * localWarpStrength);
double warpedY = y + (warpB * localWarpStrength); double warpedY = y + (warpB * localWarpStrength);
double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength);
double density = signed(localBaseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localBaseWeight; double density = localBaseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localBaseWeight;
density += signed(localDetailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localDetailWeight; if ((density + detailMin) > thresholdLimit) {
planeDensity[planeIndex] = density * normalization; planeCarve[planeIndex] = false;
continue;
}
if ((density + detailMax) <= thresholdLimit) {
planeCarve[planeIndex] = true;
continue;
}
density += localDetailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localDetailWeight;
planeCarve[planeIndex] = density <= thresholdLimit;
} }
} }
private void fillDensityPlaneWarpModules(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) { private void classifyDensityPlaneWarpModules(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
CNG localBaseDensity = baseDensity; CNG localBaseDensity = baseDensity;
CNG localDetailDensity = detailDensity; CNG localDetailDensity = detailDensity;
CNG localWarpDensity = warpDensity; CNG localWarpDensity = warpDensity;
ModuleState[] localModules = modules; Scratch scratch = SCRATCH.get();
int activeModuleCount = prepareActiveModules(scratch, y);
if (activeModuleCount == 0) {
classifyDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
return;
}
ModuleState[] localModules = scratch.activeModules;
double[] remainingMin = scratch.activeModuleRemainingMin;
double[] remainingMax = scratch.activeModuleRemainingMax;
double localBaseWeight = baseWeight; double localBaseWeight = baseWeight;
double localDetailWeight = detailWeight; double localDetailWeight = detailWeight;
double localWarpStrength = warpStrength; double localWarpStrength = warpStrength;
double normalization = inverseNormalization; double detailMin = detailMinContribution;
double detailMax = detailMaxContribution;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
int columnIndex = planeColumnIndices[planeIndex]; int columnIndex = planeColumnIndices[planeIndex];
double x = x0 + (columnIndex >> 4); double x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
double z = z0 + (columnIndex & 15); double z = z0 + (columnIndex & 15);
double warpA = signed(localWarpDensity.noiseFast3D(x, y, z)); double thresholdLimit = planeThresholdLimit[planeIndex];
double warpB = signed(localWarpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); double warpA = localWarpDensity.noiseFastSigned3D(x, y, z);
double warpB = localWarpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
double warpedX = x + (warpA * localWarpStrength); double warpedX = x + (warpA * localWarpStrength);
double warpedY = y + (warpB * localWarpStrength); double warpedY = y + (warpB * localWarpStrength);
double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength);
double density = signed(localBaseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localBaseWeight; double density = localBaseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localBaseWeight;
density += signed(localDetailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localDetailWeight; if ((density + detailMin + remainingMin[0]) > thresholdLimit) {
for (int moduleIndex = 0; moduleIndex < localModules.length; moduleIndex++) { planeCarve[planeIndex] = false;
ModuleState module = localModules[moduleIndex]; continue;
if (y < module.minY || y > module.maxY) { }
continue; if ((density + detailMax + remainingMax[0]) <= thresholdLimit) {
} planeCarve[planeIndex] = true;
continue;
double moduleDensity = signed(module.density.noiseFast3D(warpedX, warpedY, warpedZ)) - module.threshold;
if (module.invert) {
moduleDensity = -moduleDensity;
}
density += moduleDensity * module.weight;
} }
planeDensity[planeIndex] = density * normalization; density += localDetailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localDetailWeight;
if ((density + remainingMin[0]) > thresholdLimit) {
planeCarve[planeIndex] = false;
continue;
}
if ((density + remainingMax[0]) <= thresholdLimit) {
planeCarve[planeIndex] = true;
continue;
}
for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
ModuleState module = localModules[moduleIndex];
density += module.sample(warpedX, warpedY, warpedZ);
if ((density + remainingMin[moduleIndex + 1]) > thresholdLimit) {
density = Double.POSITIVE_INFINITY;
break;
}
if ((density + remainingMax[moduleIndex + 1]) <= thresholdLimit) {
density = Double.NEGATIVE_INFINITY;
break;
}
}
planeCarve[planeIndex] = density <= thresholdLimit;
} }
} }
private double sampleDensityNoWarpNoModules(int x, int y, int z) { private double sampleDensityNoWarpNoModules(int x, int y, int z) {
double density = signed(baseDensity.noiseFast3D(x, y, z)) * baseWeight; double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
density += signed(detailDensity.noiseFast3D(x, y, z)) * detailWeight; density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
return density * inverseNormalization; return density * inverseNormalization;
} }
private double sampleDensityNoWarpModules(int x, int y, int z) { private double sampleDensityNoWarpModules(int x, int y, int z) {
double density = signed(baseDensity.noiseFast3D(x, y, z)) * baseWeight; Scratch scratch = SCRATCH.get();
density += signed(detailDensity.noiseFast3D(x, y, z)) * detailWeight; int activeModuleCount = prepareActiveModules(scratch, y);
for (int moduleIndex = 0; moduleIndex < modules.length; moduleIndex++) { if (activeModuleCount == 0) {
ModuleState module = modules[moduleIndex]; return sampleDensityNoWarpNoModules(x, y, z);
if (y < module.minY || y > module.maxY) { }
continue;
}
double moduleDensity = signed(module.density.noiseFast3D(x, y, z)) - module.threshold; ModuleState[] localModules = scratch.activeModules;
double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
ModuleState module = localModules[moduleIndex];
double moduleDensity = module.density.noiseFastSigned3D(x, y, z) - module.threshold;
if (module.invert) { if (module.invert) {
moduleDensity = -moduleDensity; moduleDensity = -moduleDensity;
} }
@@ -877,31 +963,34 @@ public class IrisCaveCarver3D {
} }
private double sampleDensityWarpOnly(int x, int y, int z) { private double sampleDensityWarpOnly(int x, int y, int z) {
double warpA = signed(warpDensity.noiseFast3D(x, y, z)); double warpA = warpDensity.noiseFastSigned3D(x, y, z);
double warpB = signed(warpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
double warpedX = x + (warpA * warpStrength); double warpedX = x + (warpA * warpStrength);
double warpedY = y + (warpB * warpStrength); double warpedY = y + (warpB * warpStrength);
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
double density = signed(baseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * baseWeight; double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight;
density += signed(detailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * detailWeight; density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight;
return density * inverseNormalization; return density * inverseNormalization;
} }
private double sampleDensityWarpModules(int x, int y, int z) { private double sampleDensityWarpModules(int x, int y, int z) {
double warpA = signed(warpDensity.noiseFast3D(x, y, z)); Scratch scratch = SCRATCH.get();
double warpB = signed(warpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); int activeModuleCount = prepareActiveModules(scratch, y);
if (activeModuleCount == 0) {
return sampleDensityWarpOnly(x, y, z);
}
ModuleState[] localModules = scratch.activeModules;
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
double warpedX = x + (warpA * warpStrength); double warpedX = x + (warpA * warpStrength);
double warpedY = y + (warpB * warpStrength); double warpedY = y + (warpB * warpStrength);
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
double density = signed(baseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * baseWeight; double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight;
density += signed(detailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * detailWeight; density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight;
for (int moduleIndex = 0; moduleIndex < modules.length; moduleIndex++) { for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
ModuleState module = modules[moduleIndex]; ModuleState module = localModules[moduleIndex];
if (y < module.minY || y > module.maxY) { double moduleDensity = module.density.noiseFastSigned3D(warpedX, warpedY, warpedZ) - module.threshold;
continue;
}
double moduleDensity = signed(module.density.noiseFast3D(warpedX, warpedY, warpedZ)) - module.threshold;
if (module.invert) { if (module.invert) {
moduleDensity = -moduleDensity; moduleDensity = -moduleDensity;
} }
@@ -912,6 +1001,44 @@ public class IrisCaveCarver3D {
return density * inverseNormalization; return density * inverseNormalization;
} }
private int prepareActiveModules(Scratch scratch, int y) {
ModuleState[] configuredModules = modules;
int configuredCount = configuredModules.length;
if (configuredCount == 0) {
return 0;
}
if (scratch.activeModules.length < configuredCount) {
scratch.activeModules = new ModuleState[configuredCount];
}
int activeCount = 0;
for (int moduleIndex = 0; moduleIndex < configuredCount; moduleIndex++) {
ModuleState module = configuredModules[moduleIndex];
if (y < module.minY || y > module.maxY) {
continue;
}
scratch.activeModules[activeCount] = module;
activeCount++;
}
if (scratch.activeModuleRemainingMin.length < activeCount + 1) {
scratch.activeModuleRemainingMin = new double[activeCount + 1];
scratch.activeModuleRemainingMax = new double[activeCount + 1];
}
scratch.activeModuleRemainingMin[activeCount] = 0D;
scratch.activeModuleRemainingMax[activeCount] = 0D;
for (int moduleIndex = activeCount - 1; moduleIndex >= 0; moduleIndex--) {
ModuleState module = scratch.activeModules[moduleIndex];
scratch.activeModuleRemainingMin[moduleIndex] = scratch.activeModuleRemainingMin[moduleIndex + 1] + module.minContribution;
scratch.activeModuleRemainingMax[moduleIndex] = scratch.activeModuleRemainingMax[moduleIndex + 1] + module.maxContribution;
}
return activeCount;
}
private MatterSlice<MatterCavern> resolveCavernSlice(Scratch scratch, MantleChunk<Matter> chunk, int sectionIndex) { private MatterSlice<MatterCavern> resolveCavernSlice(Scratch scratch, MantleChunk<Matter> chunk, int sectionIndex) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
MatterSlice<MatterCavern> cachedSlice = (MatterSlice<MatterCavern>) scratch.sectionSlices[sectionIndex]; MatterSlice<MatterCavern> cachedSlice = (MatterSlice<MatterCavern>) scratch.sectionSlices[sectionIndex];
@@ -964,8 +1091,8 @@ public class IrisCaveCarver3D {
} }
private void prepareSectionCaches(Scratch scratch, int minY, int maxY) { private void prepareSectionCaches(Scratch scratch, int minY, int maxY) {
int minSection = Math.max(0, minY >> 4); int minSection = Math.max(0, PowerOfTwoCoordinates.floorDivPow2(minY, 4));
int maxSection = Math.max(minSection, maxY >> 4); int maxSection = Math.max(minSection, PowerOfTwoCoordinates.floorDivPow2(maxY, 4));
int requiredSections = maxSection + 1; int requiredSections = maxSection + 1;
if (scratch.sectionMatter.length < requiredSections) { if (scratch.sectionMatter.length < requiredSections) {
scratch.sectionMatter = new Matter[requiredSections]; scratch.sectionMatter = new Matter[requiredSections];
@@ -1038,6 +1165,8 @@ public class IrisCaveCarver3D {
private final double weight; private final double weight;
private final double threshold; private final double threshold;
private final boolean invert; private final boolean invert;
private final double minContribution;
private final double maxContribution;
private ModuleState(IrisCaveFieldModule module, CNG density) { private ModuleState(IrisCaveFieldModule module, CNG density) {
IrisRange range = module.getVerticalRange(); IrisRange range = module.getVerticalRange();
@@ -1047,6 +1176,20 @@ public class IrisCaveCarver3D {
this.weight = module.getWeight(); this.weight = module.getWeight();
this.threshold = module.getThreshold(); this.threshold = module.getThreshold();
this.invert = module.isInvert(); this.invert = module.isInvert();
double rawMin = invert ? threshold - 1D : -1D - threshold;
double rawMax = invert ? threshold + 1D : 1D - threshold;
this.minContribution = rawMin * weight;
this.maxContribution = rawMax * weight;
}
private double sample(double x, int y, double z) {
double sampled = density.noiseFastSigned3D(x, y, z);
return invert ? (threshold - sampled) * weight : (sampled - threshold) * weight;
}
private double sample(double x, double y, double z) {
double sampled = density.noiseFastSigned3D(x, y, z);
return invert ? (threshold - sampled) * weight : (sampled - threshold) * weight;
} }
} }
@@ -1061,11 +1204,15 @@ public class IrisCaveCarver3D {
private final int[] activeColumnIndices = new int[256]; private final int[] activeColumnIndices = new int[256];
private final int[] activeColumnTopY = new int[256]; private final int[] activeColumnTopY = new int[256];
private final int[] planeColumnIndices = new int[256]; private final int[] planeColumnIndices = new int[256];
private final double[] planeDensity = new double[256]; private final double[] planeThresholdLimit = new double[256];
private final boolean[] planeCarve = new boolean[256];
private final int[] tileIndices = new int[4]; private final int[] tileIndices = new int[4];
private final int[] tileLocalX = new int[4]; private final int[] tileLocalX = new int[4];
private final int[] tileLocalZ = new int[4]; private final int[] tileLocalZ = new int[4];
private final int[] tileTopY = new int[4]; private final int[] tileTopY = new int[4];
private ModuleState[] activeModules = new ModuleState[0];
private double[] activeModuleRemainingMin = new double[0];
private double[] activeModuleRemainingMax = new double[0];
private double[] verticalEdgeFade = new double[0]; private double[] verticalEdgeFade = new double[0];
private MatterCavern[] matterByY = new MatterCavern[0]; private MatterCavern[] matterByY = new MatterCavern[0];
private Matter[] sectionMatter = new Matter[0]; private Matter[] sectionMatter = new Matter[0];
@@ -33,6 +33,7 @@ import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.util.project.context.ChunkContext; import art.arcane.iris.util.project.context.ChunkContext;
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.PowerOfTwoCoordinates;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@@ -159,7 +160,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
continue; continue;
} }
int columnIndex = (localX << 4) | localZ; int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
double dominantWeight = clampWeight(dominantKernelWeight / totalKernelWeight); double dominantWeight = clampWeight(dominantKernelWeight / totalKernelWeight);
double[] weights = columnProfileWeights.get(dominantProfile); double[] weights = columnProfileWeights.get(dominantProfile);
if (weights == null) { if (weights == null) {
@@ -255,21 +256,21 @@ public class MantleCarvingComponent extends IrisMantleComponent {
} }
private void buildDimensionColumnPlan(IrisDimensionCarvingEntry[] columnPlan, int chunkX, int chunkZ, IrisDimensionCarvingEntry entry, IrisDimensionCarvingResolver.State resolverState) { private void buildDimensionColumnPlan(IrisDimensionCarvingEntry[] columnPlan, int chunkX, int chunkZ, IrisDimensionCarvingEntry entry, IrisDimensionCarvingResolver.State resolverState) {
int baseX = chunkX << 4; int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX);
int baseZ = chunkZ << 4; int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
for (int localX = 0; localX < CHUNK_SIZE; localX++) { for (int localX = 0; localX < CHUNK_SIZE; localX++) {
int worldX = baseX + localX; int worldX = baseX + localX;
for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) { for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) {
int worldZ = baseZ + localZ; int worldZ = baseZ + localZ;
int columnIndex = (localX << 4) | localZ; int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
columnPlan[columnIndex] = IrisDimensionCarvingResolver.resolveFromRoot(getEngineMantle().getEngine(), entry, worldX, worldZ, resolverState); columnPlan[columnIndex] = IrisDimensionCarvingResolver.resolveFromRoot(getEngineMantle().getEngine(), entry, worldX, worldZ, resolverState);
} }
} }
} }
private void fillProfileField(IrisCaveProfile[] profileField, int chunkX, int chunkZ, IrisComplex complex, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache) { private void fillProfileField(IrisCaveProfile[] profileField, int chunkX, int chunkZ, IrisComplex complex, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache) {
int startX = (chunkX << 4) - BLEND_RADIUS; int startX = PowerOfTwoCoordinates.chunkToBlock(chunkX) - BLEND_RADIUS;
int startZ = (chunkZ << 4) - BLEND_RADIUS; int startZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ) - BLEND_RADIUS;
for (int fieldX = 0; fieldX < FIELD_SIZE; fieldX++) { for (int fieldX = 0; fieldX < FIELD_SIZE; fieldX++) {
int worldX = startX + fieldX; int worldX = startX + fieldX;
@@ -356,8 +357,8 @@ public class MantleCarvingComponent extends IrisMantleComponent {
private int[] prepareChunkSurfaceHeights(int chunkX, int chunkZ, ChunkContext context, int[] scratch) { private int[] prepareChunkSurfaceHeights(int chunkX, int chunkZ, ChunkContext context, int[] scratch) {
int[] surfaceHeights = scratch; int[] surfaceHeights = scratch;
int baseX = chunkX << 4; int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX);
int baseZ = chunkZ << 4; int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
boolean useContextHeight = context != null boolean useContextHeight = context != null
&& context.getHeight() != null && context.getHeight() != null
&& context.getX() == baseX && context.getX() == baseX
@@ -366,7 +367,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
int worldX = baseX + localX; int worldX = baseX + localX;
for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) { for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) {
int worldZ = baseZ + localZ; int worldZ = baseZ + localZ;
int columnIndex = (localX << 4) | localZ; int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
if (useContextHeight) { if (useContextHeight) {
Double cachedHeight = context.getHeight().get(localX, localZ); Double cachedHeight = context.getHeight().get(localX, localZ);
if (cachedHeight != null) { if (cachedHeight != null) {
@@ -35,6 +35,7 @@ import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk; import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.math.BlockPosition; import art.arcane.volmlib.util.math.BlockPosition;
import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern; import art.arcane.volmlib.util.matter.MatterCavern;
@@ -89,7 +90,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
int rx = xx & 15; int rx = xx & 15;
int rz = zz & 15; int rz = zz & 15;
int columnIndex = (rx << 4) | rz; int columnIndex = PowerOfTwoCoordinates.packLocal16(rx, rz);
BlockData current = output.get(rx, yy, rz); BlockData current = output.get(rx, yy, rz);
if (B.isFluid(current)) { if (B.isFluid(current)) {
@@ -135,8 +136,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start(); PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
try { try {
walls.forEach((rx, yy, rz, cavern) -> { walls.forEach((rx, yy, rz, cavern) -> {
int worldX = rx + (x << 4); int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(x);
int worldZ = rz + (z << 4); int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(z);
String customBiome = cavern.getCustomBiome(); String customBiome = cavern.getCustomBiome();
IrisBiome biome = customBiome.isEmpty() IrisBiome biome = customBiome.isEmpty()
? resolveCaveBiome(caveBiomeCache, worldX, yy, worldZ, resolverState) ? resolveCaveBiome(caveBiomeCache, worldX, yy, worldZ, resolverState)
@@ -184,10 +185,10 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
return; return;
} }
int rx = columnIndex >> 4; int rx = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
int rz = columnIndex & 15; int rz = columnIndex & 15;
int worldX = rx + (chunkX << 4); int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX);
int worldZ = rz + (chunkZ << 4); int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ);
CaveZone zone = new CaveZone(); CaveZone zone = new CaveZone();
zone.setFloor(firstHeight); zone.setFloor(firstHeight);
int buf = firstHeight - 1; int buf = firstHeight - 1;
@@ -239,7 +240,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
} }
for (int i = zone.floor; i <= zone.ceiling; i++) { for (int i = zone.floor; i <= zone.ceiling; i++) {
MatterCavern cavernData = (MatterCavern) mc.getOrCreate(i >> 4).slice(MatterCavern.class) MatterCavern cavernData = (MatterCavern) mc.getOrCreate(PowerOfTwoCoordinates.floorDivPow2(i, 4)).slice(MatterCavern.class)
.get(rx, i & 15, rz); .get(rx, i & 15, rz);
if (cavernData != null && !cavernData.getCustomBiome().isEmpty()) { if (cavernData != null && !cavernData.getCustomBiome().isEmpty()) {
@@ -447,11 +448,11 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
} }
private int pack(int x, int y, int z) { private int pack(int x, int y, int z) {
return (y << 8) | ((x & 15) << 4) | (z & 15); return (y << 8) | PowerOfTwoCoordinates.packLocal16(x & 15, z & 15);
} }
private int unpackX(int key) { private int unpackX(int key) {
return (key >> 4) & 15; return PowerOfTwoCoordinates.unpackLocal16X(key & 255);
} }
private int unpackY(int key) { private int unpackY(int key) {
@@ -459,7 +460,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
} }
private int unpackZ(int key) { private int unpackZ(int key) {
return key & 15; return PowerOfTwoCoordinates.unpackLocal16Z(key);
} }
private int mix(int value) { private int mix(int value) {
@@ -179,14 +179,17 @@ public class IrisBiome extends IrisRegistrant implements IRare {
if (ores.isEmpty()) { if (ores.isEmpty()) {
return null; return null;
} }
BlockData b = null; KList<IrisOreGenerator> localOres = ores;
for (IrisOreGenerator i : ores) { int oreCount = localOres.size();
if (i.isGenerateSurface() != surface) for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
IrisOreGenerator oreGenerator = localOres.get(oreIndex);
if (oreGenerator.isGenerateSurface() != surface) {
continue; continue;
}
b = i.generate(x, y, z, rng, data); BlockData ore = oreGenerator.generate(x, y, z, rng, data);
if (b != null) { if (ore != null) {
return b; return ore;
} }
} }
return null; return null;
@@ -77,7 +77,10 @@ public class IrisBiomePaletteLayer {
return getBlockData(data).get(0); return getBlockData(data).get(0);
} }
return getLayerGenerator(rng, data).fit(getBlockData(data), x / zoom, y / zoom, z / zoom); double scaledX = x / zoom;
double scaledY = y / zoom;
double scaledZ = z / zoom;
return getLayerGenerator(rng, data).fit(getBlockData(data), scaledX, scaledY, scaledZ);
} }
public CNG getLayerGenerator(RNG rng, IrisData data) { public CNG getLayerGenerator(RNG rng, IrisData data) {
@@ -290,22 +290,25 @@ public class IrisDimension extends IrisRegistrant {
carvingEntryIndex.reset(); carvingEntryIndex.reset();
} }
public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) { public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) {
if (ores.isEmpty()) { if (ores.isEmpty()) {
return null; return null;
} }
BlockData b = null; KList<IrisOreGenerator> localOres = ores;
for (IrisOreGenerator i : ores) { int oreCount = localOres.size();
if (i.isGenerateSurface() != surface) for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
continue; IrisOreGenerator oreGenerator = localOres.get(oreIndex);
if (oreGenerator.isGenerateSurface() != surface) {
b = i.generate(x, y, z, rng, data); continue;
if (b != null) { }
return b;
} BlockData ore = oreGenerator.generate(x, y, z, rng, data);
} if (ore != null) {
return null; return ore;
} }
}
return null;
}
public int getFluidHeight() { public int getFluidHeight() {
return fluidHeight - (int) dimensionHeight.getMin(); return fluidHeight - (int) dimensionHeight.getMin();
@@ -212,21 +212,23 @@ public class IrisGenerator extends IrisRegistrant {
int hc = (int) ((cliffHeightMin * 10) + 10 + cliffHeightMax * getSeed() + offsetX + offsetZ); int hc = (int) ((cliffHeightMin * 10) + 10 + cliffHeightMax * getSeed() + offsetX + offsetZ);
double h = multiplicitive ? 1 : 0; double h = multiplicitive ? 1 : 0;
double tp = 0; double tp = 0;
double sampleX = (rx + offsetX) / zoom;
double sampleZ = (rz + offsetZ) / zoom;
if (composite.size() == 1) { if (composite.size() == 1) {
if (multiplicitive) { if (multiplicitive) {
h *= composite.get(0).getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader()); h *= composite.get(0).getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
} else { } else {
tp += composite.get(0).getOpacity(); tp += composite.get(0).getOpacity();
h += composite.get(0).getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader()); h += composite.get(0).getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
} }
} else { } else {
for (IrisNoiseGenerator i : composite) { for (IrisNoiseGenerator i : composite) {
if (multiplicitive) { if (multiplicitive) {
h *= i.getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader()); h *= i.getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
} else { } else {
tp += i.getOpacity(); tp += i.getOpacity();
h += i.getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader()); h += i.getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
} }
} }
} }
@@ -245,7 +247,9 @@ public class IrisGenerator extends IrisRegistrant {
public double cell(double rx, double rz, double v, double superSeed) { public double cell(double rx, double rz, double v, double superSeed) {
getCellGenerator(getSeed() + 46222).setShuffle(getCellFractureShuffle()); getCellGenerator(getSeed() + 46222).setShuffle(getCellFractureShuffle());
return getCellGenerator(getSeed() + 46222).getDistance(rx / getCellFractureZoom(), rz / getCellFractureZoom()) > getCellPercentSize() ? (v * getCellFractureHeight()) : v; double fractureX = rx / getCellFractureZoom();
double fractureZ = rz / getCellFractureZoom();
return getCellGenerator(getSeed() + 46222).getDistance(fractureX, fractureZ) > getCellPercentSize() ? (v * getCellFractureHeight()) : v;
} }
private boolean hasCellCracks() { private boolean hasCellCracks() {
@@ -254,7 +258,9 @@ public class IrisGenerator extends IrisRegistrant {
public double getCliffHeight(double rx, double rz, double superSeed) { public double getCliffHeight(double rx, double rz, double superSeed) {
int hc = (int) ((cliffHeightMin * 10) + 10 + cliffHeightMax * getSeed() + offsetX + offsetZ); int hc = (int) ((cliffHeightMin * 10) + 10 + cliffHeightMax * getSeed() + offsetX + offsetZ);
double h = cliffHeightGenerator.getNoise((long) (getSeed() + superSeed + hc), (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader()); double sampleX = (rx + offsetX) / zoom;
double sampleZ = (rz + offsetZ) / zoom;
double h = cliffHeightGenerator.getNoise((long) (getSeed() + superSeed + hc), sampleX, sampleZ, getLoader());
return IrisInterpolation.lerp(cliffHeightMin, cliffHeightMax, h); return IrisInterpolation.lerp(cliffHeightMin, cliffHeightMax, h);
} }
@@ -33,6 +33,7 @@ import lombok.experimental.Accessors;
import java.io.File; import java.io.File;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@Snippet("style") @Snippet("style")
@Accessors(chain = true) @Accessors(chain = true)
@@ -41,6 +42,7 @@ import java.util.Objects;
@Desc("A gen style") @Desc("A gen style")
@Data @Data
public class IrisGeneratorStyle { public class IrisGeneratorStyle {
private static final ConcurrentHashMap<String, String> ACTIVE_CACHE_KEYS = new ConcurrentHashMap<>();
private final transient AtomicCache<CNG> cng = new AtomicCache<>(); private final transient AtomicCache<CNG> cng = new AtomicCache<>();
@Desc("The chance is 1 in CHANCE per interval") @Desc("The chance is 1 in CHANCE per interval")
private NoiseStyle style = NoiseStyle.FLAT; private NoiseStyle style = NoiseStyle.FLAT;
@@ -127,6 +129,11 @@ public class IrisGeneratorStyle {
private void clearStaleCacheEntries(IrisData data, String prefix, String key) { private void clearStaleCacheEntries(IrisData data, String prefix, String key) {
File cacheFolder = new File(data.getDataFolder(), ".cache"); File cacheFolder = new File(data.getDataFolder(), ".cache");
String cacheFolderKey = cacheFolder.getAbsolutePath() + File.separator + prefix;
String previousKey = ACTIVE_CACHE_KEYS.put(cacheFolderKey, key);
if (key.equals(previousKey)) {
return;
}
File[] files = cacheFolder.listFiles((dir, name) -> name.endsWith(".cnm") && name.startsWith(prefix)); File[] files = cacheFolder.listFiles((dir, name) -> name.endsWith(".cnm") && name.startsWith(prefix));
if (files == null) { if (files == null) {
return; return;
@@ -61,7 +61,10 @@ public class IrisMaterialPalette {
return getBlockData(rdata).get(0); return getBlockData(rdata).get(0);
} }
return getLayerGenerator(rng, rdata).fit(getBlockData(rdata), x / zoom, y / zoom, z / zoom); double scaledX = x / zoom;
double scaledY = y / zoom;
double scaledZ = z / zoom;
return getLayerGenerator(rng, rdata).fit(getBlockData(rdata), scaledX, scaledY, scaledZ);
} }
public Optional<TileData> getTile(RNG rng, double x, double y, double z, IrisData rdata) { public Optional<TileData> getTile(RNG rng, double x, double y, double z, IrisData rdata) {
@@ -22,8 +22,8 @@ import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.data.cache.AtomicCache; import art.arcane.iris.engine.data.cache.AtomicCache;
import art.arcane.iris.engine.object.annotations.*; import art.arcane.iris.engine.object.annotations.*;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
import art.arcane.iris.util.project.noise.CNG; import art.arcane.iris.util.project.noise.CNG;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -100,14 +100,17 @@ public class IrisNoiseGenerator {
for (IrisNoiseGenerator i : fracture) { for (IrisNoiseGenerator i : fracture) {
if (i.isEnabled()) { if (i.isEnabled()) {
x += i.getNoise(superSeed + seed + g, xv, zv, data) - (i.getOpacity() / 2D); double fractureOffset = i.getOpacity() / 2D;
z += i.getNoise(superSeed + seed + g, zv, xv, data) - (i.getOpacity() / 2D); x += i.getNoise(superSeed + seed + g, xv, zv, data) - fractureOffset;
z += i.getNoise(superSeed + seed + g, zv, xv, data) - fractureOffset;
} }
g += 819; g += 819;
} }
CNG cng = getGenerator(superSeed, data); CNG cng = getGenerator(superSeed, data);
double n = cng.noiseFast2D((x / zoom) + offsetX, (z / zoom) + offsetZ) * opacity; double sampleX = (x / zoom) + offsetX;
double sampleZ = (z / zoom) + offsetZ;
double n = cng.noiseFast2D(sampleX, sampleZ) * opacity;
n = negative ? (-n + opacity) : n; n = negative ? (-n + opacity) : n;
n = (exponent != 1 ? n < 0 ? -Math.pow(-n, exponent) : Math.pow(n, exponent) : n) + offsetY; n = (exponent != 1 ? n < 0 ? -Math.pow(-n, exponent) : Math.pow(n, exponent) : n) + offsetY;
n = parametric ? IrisInterpolation.parametric(n, 1) : n; n = parametric ? IrisInterpolation.parametric(n, 1) : n;
@@ -155,14 +155,17 @@ public class IrisRegion extends IrisRegistrant implements IRare {
if (ores.isEmpty()) { if (ores.isEmpty()) {
return null; return null;
} }
BlockData b = null; KList<IrisOreGenerator> localOres = ores;
for (IrisOreGenerator i : ores) { int oreCount = localOres.size();
if (i.isGenerateSurface() != surface) for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
IrisOreGenerator oreGenerator = localOres.get(oreIndex);
if (oreGenerator.isGenerateSurface() != surface) {
continue; continue;
}
b = i.generate(x, y, z, rng, data); BlockData ore = oreGenerator.generate(x, y, z, rng, data);
if (b != null) { if (ore != null) {
return b; return ore;
} }
} }
return null; return null;
@@ -21,6 +21,7 @@ package art.arcane.iris.engine.platform;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.IrisWorlds; import art.arcane.iris.core.IrisWorlds;
import art.arcane.iris.core.gui.PregeneratorJob;
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.core.service.StudioSVC; import art.arcane.iris.core.service.StudioSVC;
@@ -81,6 +82,8 @@ import java.util.concurrent.locks.ReentrantLock;
@Data @Data
public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener { public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener {
private static final int LOAD_LOCKS = Runtime.getRuntime().availableProcessors() * 4; private static final int LOAD_LOCKS = Runtime.getRuntime().availableProcessors() * 4;
private static final long HOTLOAD_LOOP_DELAY_MS = 250L;
private static final long HOTLOAD_MAINTENANCE_DELAY_MS = 4000L;
private final Semaphore loadLock; private final Semaphore loadLock;
private final IrisWorld world; private final IrisWorld world;
private final File dataLocation; private final File dataLocation;
@@ -563,11 +566,15 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
this.hotloader = studio ? new Looper() { this.hotloader = studio ? new Looper() {
@Override @Override
protected long loop() { protected long loop() {
if (shouldThrottleHotload()) {
return HOTLOAD_MAINTENANCE_DELAY_MS;
}
if (hotloadChecker.flip()) { if (hotloadChecker.flip()) {
folder.check(); folder.check();
} }
return 250; return HOTLOAD_LOOP_DELAY_MS;
} }
} : null; } : null;
@@ -735,10 +742,24 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
return true; return true;
} }
return isMaintenanceActive();
}
private boolean isMaintenanceActive() {
World realWorld = this.world.realWorld(); World realWorld = this.world.realWorld();
return realWorld != null && IrisToolbelt.isWorldMaintenanceActive(realWorld); return realWorld != null && IrisToolbelt.isWorldMaintenanceActive(realWorld);
} }
private boolean shouldThrottleHotload() {
if (isMaintenanceActive()) {
return true;
}
World realWorld = this.world.realWorld();
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
return realWorld != null && pregeneratorJob != null && pregeneratorJob.targetsWorld(realWorld);
}
@Override @Override
public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) { public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) {
Engine currentEngine = engine; Engine currentEngine = engine;
@@ -19,6 +19,7 @@
package art.arcane.iris.util.common.data; package art.arcane.iris.util.common.data;
import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
public class BiomeMap { public class BiomeMap {
private final IrisBiome[] height; private final IrisBiome[] height;
@@ -28,10 +29,10 @@ public class BiomeMap {
} }
public void setBiome(int x, int z, IrisBiome h) { public void setBiome(int x, int z, IrisBiome h) {
height[x * 16 + z] = h; height[PowerOfTwoCoordinates.packLocal16(x, z)] = h;
} }
public IrisBiome getBiome(int x, int z) { public IrisBiome getBiome(int x, int z) {
return height[x * 16 + z]; return height[PowerOfTwoCoordinates.packLocal16(x, z)];
} }
} }
@@ -1,7 +1,7 @@
package art.arcane.iris.util.project.context; package art.arcane.iris.util.project.context;
import art.arcane.iris.util.project.stream.ProceduralStream; import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.iris.util.project.stream.utility.CachedStream2D; import art.arcane.iris.util.project.stream.utility.ChunkFillableStream2D;
import art.arcane.volmlib.util.documentation.BlockCoordinates; import art.arcane.volmlib.util.documentation.BlockCoordinates;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -35,8 +35,8 @@ public class ChunkedDataCache<T> {
return; return;
} }
if (stream instanceof CachedStream2D<?> cachedStream) { if (stream instanceof ChunkFillableStream2D cachedStream) {
cachedStream.fillChunk(x, z, data); cachedStream.fillChunkRaw(x, z, data);
return; return;
} }
@@ -32,7 +32,6 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
public class IrisInterpolation { public class IrisInterpolation {
public static CNG cng = NoiseStyle.SIMPLEX.create(new RNG()); public static CNG cng = NoiseStyle.SIMPLEX.create(new RNG());
@@ -952,18 +951,10 @@ public class IrisInterpolation {
*/ */
public static Hunk<Double> getNoise3D(InterpolationMethod3D method, int xo, int yo, int zo, int w, int h, int d, double radX, double radY, double radZ, NoiseProvider3 n) { public static Hunk<Double> getNoise3D(InterpolationMethod3D method, int xo, int yo, int zo, int w, int h, int d, double radX, double radY, double radZ, NoiseProvider3 n) {
Hunk<Double> hunk = Hunk.newAtomicDoubleHunk(w, h, d); Hunk<Double> hunk = Hunk.newAtomicDoubleHunk(w, h, d);
HashMap<Integer, Double> cache = new HashMap<>(); for (int i = 0; i < w; i++) {
int i, j, k; for (int j = 0; j < h; j++) {
for (int k = 0; k < d; k++) {
for (i = 0; i < w; i++) { hunk.set(i, j, k, getNoise3D(method, i + xo, j + yo, k + zo, radX, radY, radZ, n));
int fi = i;
for (j = 0; j < h; j++) {
int fj = j;
for (k = 0; k < d; k++) {
int fk = k;
hunk.set(i, j, k, cache.computeIfAbsent((k * w * h) + (j * w) + i, (p)
-> getNoise3D(method, fi + xo, fj + yo, fk + zo,
radX, radY, radZ, n)));
} }
} }
} }
@@ -74,6 +74,10 @@ public class CNG {
private NoiseStyle leakStyle; private NoiseStyle leakStyle;
private ProceduralStream<Double> customGenerator; private ProceduralStream<Double> customGenerator;
private transient boolean identityPostFastPath; private transient boolean identityPostFastPath;
private transient boolean identitySignedFastPath;
private transient boolean unityOpacity;
private transient double effectiveScale;
private transient double signedFractureScale;
private transient boolean fastPathStateDirty = true; private transient boolean fastPathStateDirty = true;
public CNG(RNG random) { public CNG(RNG random) {
@@ -635,7 +639,8 @@ public class CNG {
} }
private double getNoise(double... dim) { private double getNoise(double... dim) {
double scale = noscale ? 1 : this.bakedScale * this.scale; ensureFastPathState();
double scale = effectiveScale;
if (fracture == null || noscale) { if (fracture == null || noscale) {
return generator.noise( return generator.noise(
@@ -671,7 +676,8 @@ public class CNG {
} }
private double getNoise(double x) { private double getNoise(double x) {
double scl = noscale ? 1 : this.bakedScale * this.scale; ensureFastPathState();
double scl = effectiveScale;
if (fracture == null || noscale) { if (fracture == null || noscale) {
return generator.noise(x * scl, 0D, 0D) * opacity; return generator.noise(x * scl, 0D, 0D) * opacity;
@@ -682,7 +688,8 @@ public class CNG {
} }
private double getNoise(double x, double z) { private double getNoise(double x, double z) {
double scl = noscale ? 1 : this.bakedScale * this.scale; ensureFastPathState();
double scl = effectiveScale;
if (fracture == null || noscale) { if (fracture == null || noscale) {
return generator.noise(x * scl, z * scl, 0D) * opacity; return generator.noise(x * scl, z * scl, 0D) * opacity;
@@ -694,7 +701,8 @@ public class CNG {
} }
private double getNoise(double x, double y, double z) { private double getNoise(double x, double y, double z) {
double scl = noscale ? 1 : this.bakedScale * this.scale; ensureFastPathState();
double scl = effectiveScale;
if (fracture == null || noscale) { if (fracture == null || noscale) {
return generator.noise(x * scl, y * scl, z * scl) * opacity; return generator.noise(x * scl, y * scl, z * scl) * opacity;
@@ -932,6 +940,18 @@ public class CNG {
return applyPost(getNoise(x, z), x, z); return applyPost(getNoise(x, z), x, z);
} }
public double noiseFastSigned2D(double x, double z) {
if (cache != null && isWholeCoordinate(x) && isWholeCoordinate(z)) {
return (cache.get((int) x, (int) z) * 2D) - 1D;
}
if (hasIdentitySignedFastPath()) {
return getSignedNoise(x, z);
}
return (noiseFast2D(x, z) * 2D) - 1D;
}
public double noise(double x, double y, double z) { public double noise(double x, double y, double z) {
return applyPost(getNoise(x, y, z), x, y, z); return applyPost(getNoise(x, y, z), x, y, z);
} }
@@ -944,6 +964,14 @@ public class CNG {
return applyPost(getNoise(x, y, z), x, y, z); return applyPost(getNoise(x, y, z), x, y, z);
} }
public double noiseFastSigned3D(double x, double y, double z) {
if (hasIdentitySignedFastPath()) {
return getSignedNoise(x, y, z);
}
return (noiseFast3D(x, y, z) * 2D) - 1D;
}
public CNG pow(double power) { public CNG pow(double power) {
this.power = power; this.power = power;
markFastPathStateDirty(); markFastPathStateDirty();
@@ -964,11 +992,19 @@ public class CNG {
} }
private boolean isIdentityPostFastPath() { private boolean isIdentityPostFastPath() {
ensureFastPathState();
return identityPostFastPath;
}
private boolean hasIdentitySignedFastPath() {
ensureFastPathState();
return identitySignedFastPath;
}
private void ensureFastPathState() {
if (fastPathStateDirty) { if (fastPathStateDirty) {
refreshFastPathState(); refreshFastPathState();
} }
return identityPostFastPath;
} }
private void markFastPathStateDirty() { private void markFastPathStateDirty() {
@@ -976,15 +1012,62 @@ public class CNG {
} }
private void refreshFastPathState() { private void refreshFastPathState() {
effectiveScale = noscale ? 1D : bakedScale * scale;
identityPostFastPath = power == 1D identityPostFastPath = power == 1D
&& children == null && children == null
&& fracture == null && fracture == null
&& down == 0D && down == 0D
&& up == 0D && up == 0D
&& patch == 1D; && patch == 1D;
identitySignedFastPath = power == 1D
&& children == null
&& down == 0D
&& up == 0D
&& patch == 1D;
unityOpacity = opacity == 1D;
signedFractureScale = 0.5D * fscale;
fastPathStateDirty = false; fastPathStateDirty = false;
} }
private double signedWithOpacity(double signedNoise) {
if (unityOpacity) {
return signedNoise;
}
return ((signedNoise + 1D) * opacity) - 1D;
}
private double getSignedNoise(double x, double z) {
ensureFastPathState();
double scl = effectiveScale;
NoiseGenerator localGenerator = generator;
CNG localFracture = fracture;
if (localFracture == null || noscale) {
return signedWithOpacity(localGenerator.noiseSigned(x * scl, z * scl, 0D));
}
double fx = x + (localFracture.noiseFastSigned2D(x, z) * signedFractureScale);
double fz = z + (localFracture.noiseFastSigned2D(z, x) * signedFractureScale);
return signedWithOpacity(localGenerator.noiseSigned(fx * scl, fz * scl, 0D));
}
private double getSignedNoise(double x, double y, double z) {
ensureFastPathState();
double scl = effectiveScale;
NoiseGenerator localGenerator = generator;
CNG localFracture = fracture;
if (localFracture == null || noscale) {
return signedWithOpacity(localGenerator.noiseSigned(x * scl, y * scl, z * scl));
}
double fx = x + (localFracture.noiseFastSigned3D(x, y, z) * signedFractureScale);
double fy = y + (localFracture.noiseFastSigned2D(y, x) * signedFractureScale);
double fz = z + (localFracture.noiseFastSigned3D(z, x, y) * signedFractureScale);
return signedWithOpacity(localGenerator.noiseSigned(fx * scl, fy * scl, fz * scl));
}
private enum InjectorMode { private enum InjectorMode {
ADD, ADD,
SRC_SUBTRACT, SRC_SUBTRACT,
File diff suppressed because one or more lines are too long
@@ -38,16 +38,31 @@ public class FractalFBMSimplexNoise implements NoiseGenerator, OctaveNoise {
return f(n.GetSimplexFractal(x, 0d)); return f(n.GetSimplexFractal(x, 0d));
} }
@Override
public double noiseSigned(double x) {
return n.GetSimplexFractal(x, 0D);
}
@Override @Override
public double noise(double x, double z) { public double noise(double x, double z) {
return f(n.GetSimplexFractal(x, z)); return f(n.GetSimplexFractal(x, z));
} }
@Override
public double noiseSigned(double x, double z) {
return n.GetSimplexFractal(x, z);
}
@Override @Override
public double noise(double x, double y, double z) { public double noise(double x, double y, double z) {
return f(n.GetSimplexFractal(x, y, z)); return f(n.GetSimplexFractal(x, y, z));
} }
@Override
public double noiseSigned(double x, double y, double z) {
return n.GetSimplexFractal(x, y, z);
}
@Override @Override
public void setOctaves(int o) { public void setOctaves(int o) {
n.setFractalOctaves(o); n.setFractalOctaves(o);
@@ -24,6 +24,7 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
{-1L, 1L}, {-1L, 1L},
{0L, 1L} {0L, 1L}
}; };
private static final ThreadLocal<HexScratch> SCRATCH = ThreadLocal.withInitial(HexScratch::new);
private final long seed; private final long seed;
private final SimplexNoise heatSimplex; private final SimplexNoise heatSimplex;
private int octaves; private int octaves;
@@ -221,6 +222,9 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
} }
private double sample(double x, double z) { private double sample(double x, double z) {
HexScratch scratch = SCRATCH.get();
double[] centersQ = scratch.centersQ;
double[] centersR = scratch.centersR;
double qWorld = (SQRT_3_OVER_3 * x) - (z / 3.0); double qWorld = (SQRT_3_OVER_3 * x) - (z / 3.0);
double rWorld = TWO_OVER_THREE * z; double rWorld = TWO_OVER_THREE * z;
long[] rounded = roundAxial(qWorld, rWorld); long[] rounded = roundAxial(qWorld, rWorld);
@@ -233,8 +237,6 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
double heat = cellHeat(centerQ, centerR, nodeHash, 0); double heat = cellHeat(centerQ, centerR, nodeHash, 0);
for (int level = 0; level < MAX_DEPTH; level++) { for (int level = 0; level < MAX_DEPTH; level++) {
double[] centersQ = new double[7];
double[] centersR = new double[7];
int childIndex = pickChildIndex(localQ, localR, centersQ, centersR); int childIndex = pickChildIndex(localQ, localR, centersQ, centersR);
if (childIndex < 0) { if (childIndex < 0) {
@@ -285,4 +287,9 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
public void setOctaves(int o) { public void setOctaves(int o) {
this.octaves = Math.max(1, o); this.octaves = Math.max(1, o);
} }
private static final class HexScratch {
private final double[] centersQ = new double[7];
private final double[] centersR = new double[7];
}
} }
@@ -24,6 +24,7 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
{-1L, 1L}, {-1L, 1L},
{0L, 1L} {0L, 1L}
}; };
private static final ThreadLocal<HexScratch> SCRATCH = ThreadLocal.withInitial(HexScratch::new);
private final long seed; private final long seed;
private final SimplexNoise heatSimplex; private final SimplexNoise heatSimplex;
private final SimplexNoise structureSimplex; private final SimplexNoise structureSimplex;
@@ -186,6 +187,9 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
} }
private double sample(double x, double z) { private double sample(double x, double z) {
HexScratch scratch = SCRATCH.get();
double[] centersQ = scratch.centersQ;
double[] centersR = scratch.centersR;
double qWorld = (SQRT_3_OVER_3 * x) - (z / 3.0); double qWorld = (SQRT_3_OVER_3 * x) - (z / 3.0);
double rWorld = TWO_OVER_THREE * z; double rWorld = TWO_OVER_THREE * z;
long[] rounded = roundAxial(qWorld, rWorld); long[] rounded = roundAxial(qWorld, rWorld);
@@ -202,8 +206,6 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
return heat; return heat;
} }
double[] centersQ = new double[7];
double[] centersR = new double[7];
int childIndex = pickChildIndex(localQ, localR, centersQ, centersR); int childIndex = pickChildIndex(localQ, localR, centersQ, centersR);
if (childIndex < 0) { if (childIndex < 0) {
@@ -250,4 +252,9 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
public void setOctaves(int o) { public void setOctaves(int o) {
this.octaves = Math.max(1, o); this.octaves = Math.max(1, o);
} }
private static final class HexScratch {
private final double[] centersQ = new double[7];
private final double[] centersR = new double[7];
}
} }
@@ -27,6 +27,18 @@ public interface NoiseGenerator {
double noise(double x, double y, double z); double noise(double x, double y, double z);
default double noiseSigned(double x) {
return (noise(x) * 2D) - 1D;
}
default double noiseSigned(double x, double z) {
return (noise(x, z) * 2D) - 1D;
}
default double noiseSigned(double x, double y, double z) {
return (noise(x, y, z) * 2D) - 1D;
}
default boolean isStatic() { default boolean isStatic() {
return false; return false;
} }
@@ -6,11 +6,31 @@ import org.jetbrains.annotations.NotNull;
public class OffsetNoiseGenerator implements NoiseGenerator { public class OffsetNoiseGenerator implements NoiseGenerator {
private static final int GENERIC = 0;
private static final int SIMPLEX = 1;
private static final int PERLIN = 2;
private static final int FRACTAL_FBM_SIMPLEX = 3;
private static final int VASCULAR = 4;
private final NoiseGenerator base; private final NoiseGenerator base;
private final double ox, oz; private final SimplexNoise simplexBase;
private final PerlinNoise perlinBase;
private final FractalFBMSimplexNoise fractalFbmSimplexBase;
private final VascularNoise vascularBase;
private final int dispatchKind;
private final double ox;
private final double oz;
public OffsetNoiseGenerator(NoiseGenerator base, long seed) { public OffsetNoiseGenerator(NoiseGenerator base, long seed) {
this.base = base; this.base = base;
this.simplexBase = base instanceof SimplexNoise simplex ? simplex : null;
this.perlinBase = base instanceof PerlinNoise perlin ? perlin : null;
this.fractalFbmSimplexBase = base instanceof FractalFBMSimplexNoise fractal ? fractal : null;
this.vascularBase = base instanceof VascularNoise vascular ? vascular : null;
this.dispatchKind = simplexBase != null ? SIMPLEX
: perlinBase != null ? PERLIN
: fractalFbmSimplexBase != null ? FRACTAL_FBM_SIMPLEX
: vascularBase != null ? VASCULAR
: GENERIC;
RNG rng = new RNG(seed); RNG rng = new RNG(seed);
ox = rng.nextInt(Short.MIN_VALUE, Short.MAX_VALUE); ox = rng.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
oz = rng.nextInt(Short.MIN_VALUE, Short.MAX_VALUE); oz = rng.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
@@ -18,17 +38,68 @@ public class OffsetNoiseGenerator implements NoiseGenerator {
@Override @Override
public double noise(double x) { public double noise(double x) {
return base.noise(x + ox); return switch (dispatchKind) {
case SIMPLEX -> simplexBase.noise(x + ox);
case PERLIN -> perlinBase.noise(x + ox);
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noise(x + ox);
case VASCULAR -> vascularBase.noise(x + ox);
default -> base.noise(x + ox);
};
}
@Override
public double noiseSigned(double x) {
return switch (dispatchKind) {
case SIMPLEX -> simplexBase.noiseSigned(x + ox);
case PERLIN -> perlinBase.noiseSigned(x + ox);
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noiseSigned(x + ox);
case VASCULAR -> vascularBase.noiseSigned(x + ox);
default -> base.noiseSigned(x + ox);
};
} }
@Override @Override
public double noise(double x, double z) { public double noise(double x, double z) {
return base.noise(x + ox, z + oz); return switch (dispatchKind) {
case SIMPLEX -> simplexBase.noise(x + ox, z + oz);
case PERLIN -> perlinBase.noise(x + ox, z + oz);
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noise(x + ox, z + oz);
case VASCULAR -> vascularBase.noise(x + ox, z + oz);
default -> base.noise(x + ox, z + oz);
};
}
@Override
public double noiseSigned(double x, double z) {
return switch (dispatchKind) {
case SIMPLEX -> simplexBase.noiseSigned(x + ox, z + oz);
case PERLIN -> perlinBase.noiseSigned(x + ox, z + oz);
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noiseSigned(x + ox, z + oz);
case VASCULAR -> vascularBase.noiseSigned(x + ox, z + oz);
default -> base.noiseSigned(x + ox, z + oz);
};
} }
@Override @Override
public double noise(double x, double y, double z) { public double noise(double x, double y, double z) {
return base.noise(x + ox, y, z + oz); return switch (dispatchKind) {
case SIMPLEX -> simplexBase.noise(x + ox, y, z + oz);
case PERLIN -> perlinBase.noise(x + ox, y, z + oz);
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noise(x + ox, y, z + oz);
case VASCULAR -> vascularBase.noise(x + ox, y, z + oz);
default -> base.noise(x + ox, y, z + oz);
};
}
@Override
public double noiseSigned(double x, double y, double z) {
return switch (dispatchKind) {
case SIMPLEX -> simplexBase.noiseSigned(x + ox, y, z + oz);
case PERLIN -> perlinBase.noiseSigned(x + ox, y, z + oz);
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noiseSigned(x + ox, y, z + oz);
case VASCULAR -> vascularBase.noiseSigned(x + ox, y, z + oz);
default -> base.noiseSigned(x + ox, y, z + oz);
};
} }
@Override @Override
@@ -45,4 +116,4 @@ public class OffsetNoiseGenerator implements NoiseGenerator {
public NoiseGenerator getBase() { public NoiseGenerator getBase() {
return base; return base;
} }
} }
@@ -51,6 +51,33 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise {
return f(v / m); return f(v / m);
} }
@Override
public double noiseSigned(double x) {
if (octaves <= 1) {
return n.GetPerlin(x, 0D);
}
double frequency = 1D;
double magnitude = 0D;
double value = 0D;
for (int i = 0; i < octaves; i++) {
double sampleFrequency;
if (frequency == 1D) {
sampleFrequency = frequency;
frequency = 2D;
} else {
frequency *= 2D;
sampleFrequency = frequency;
}
value += n.GetPerlin(x * sampleFrequency, 0D) * frequency;
magnitude += frequency;
}
return value / magnitude;
}
@Override @Override
public double noise(double x, double z) { public double noise(double x, double z) {
if (octaves <= 1) { if (octaves <= 1) {
@@ -69,6 +96,26 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise {
return f(v / m); return f(v / m);
} }
@Override
public double noiseSigned(double x, double z) {
if (octaves <= 1) {
return n.GetPerlin(x, z);
}
double frequency = 1D;
double magnitude = 0D;
double value = 0D;
for (int i = 0; i < octaves; i++) {
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
value += n.GetPerlin(x * octaveFrequency, z * octaveFrequency) * octaveFrequency;
magnitude += octaveFrequency;
frequency = octaveFrequency;
}
return value / magnitude;
}
@Override @Override
public double noise(double x, double y, double z) { public double noise(double x, double y, double z) {
if (octaves <= 1) { if (octaves <= 1) {
@@ -87,6 +134,26 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise {
return f(v / m); return f(v / m);
} }
@Override
public double noiseSigned(double x, double y, double z) {
if (octaves <= 1) {
return n.GetPerlin(x, y, z);
}
double frequency = 1D;
double magnitude = 0D;
double value = 0D;
for (int i = 0; i < octaves; i++) {
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
value += n.GetPerlin(x * octaveFrequency, y * octaveFrequency, z * octaveFrequency) * octaveFrequency;
magnitude += octaveFrequency;
frequency = octaveFrequency;
}
return value / magnitude;
}
@Override @Override
public void setOctaves(int o) { public void setOctaves(int o) {
octaves = o; octaves = o;
@@ -51,6 +51,33 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise {
return f(v / m); return f(v / m);
} }
@Override
public double noiseSigned(double x) {
if (octaves <= 1) {
return n.GetSimplex(x, 0D);
}
double frequency = 1D;
double magnitude = 0D;
double value = 0D;
for (int i = 0; i < octaves; i++) {
double sampleFrequency;
if (frequency == 1D) {
sampleFrequency = frequency;
frequency = 2D;
} else {
frequency *= 2D;
sampleFrequency = frequency;
}
value += n.GetSimplex(x * sampleFrequency, 0D) * frequency;
magnitude += frequency;
}
return value / magnitude;
}
@Override @Override
public double noise(double x, double z) { public double noise(double x, double z) {
if (octaves <= 1) { if (octaves <= 1) {
@@ -69,6 +96,26 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise {
return f(v / m); return f(v / m);
} }
@Override
public double noiseSigned(double x, double z) {
if (octaves <= 1) {
return n.GetSimplex(x, z);
}
double frequency = 1D;
double magnitude = 0D;
double value = 0D;
for (int i = 0; i < octaves; i++) {
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
value += n.GetSimplex(x * octaveFrequency, z * octaveFrequency) * octaveFrequency;
magnitude += octaveFrequency;
frequency = octaveFrequency;
}
return value / magnitude;
}
@Override @Override
public double noise(double x, double y, double z) { public double noise(double x, double y, double z) {
if (octaves <= 1) { if (octaves <= 1) {
@@ -87,6 +134,26 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise {
return f(v / m); return f(v / m);
} }
@Override
public double noiseSigned(double x, double y, double z) {
if (octaves <= 1) {
return n.GetSimplex(x, y, z);
}
double frequency = 1D;
double magnitude = 0D;
double value = 0D;
for (int i = 0; i < octaves; i++) {
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
value += n.GetSimplex(x * octaveFrequency, y * octaveFrequency, z * octaveFrequency) * octaveFrequency;
magnitude += octaveFrequency;
frequency = octaveFrequency;
}
return value / magnitude;
}
@Override @Override
public void setOctaves(int o) { public void setOctaves(int o) {
octaves = o; octaves = o;
@@ -18,7 +18,6 @@
package art.arcane.iris.util.project.noise; package art.arcane.iris.util.project.noise;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
public class VascularNoise implements NoiseGenerator { public class VascularNoise implements NoiseGenerator {
@@ -32,7 +31,20 @@ public class VascularNoise implements NoiseGenerator {
} }
private double filter(double noise) { private double filter(double noise) {
return M.clip((noise / 2D) + 0.5D, 0D, 1D); double normalized = (noise * 0.5D) + 0.5D;
if (normalized < 0D) {
return 0D;
}
return normalized > 1D ? 1D : normalized;
}
private double filterSigned(double noise) {
if (noise < -1D) {
return -1D;
}
return noise > 1D ? 1D : noise;
} }
@Override @Override
@@ -40,13 +52,28 @@ public class VascularNoise implements NoiseGenerator {
return filter(n.GetCellular(x, 0)); return filter(n.GetCellular(x, 0));
} }
@Override
public double noiseSigned(double x) {
return filterSigned(n.GetCellular(x, 0D));
}
@Override @Override
public double noise(double x, double z) { public double noise(double x, double z) {
return filter(n.GetCellular(x, z)); return filter(n.GetCellular(x, z));
} }
@Override
public double noiseSigned(double x, double z) {
return filterSigned(n.GetCellular(x, z));
}
@Override @Override
public double noise(double x, double y, double z) { public double noise(double x, double y, double z) {
return filter(n.GetCellular(x, y, z)); return filter(n.GetCellular(x, y, z));
} }
@Override
public double noiseSigned(double x, double y, double z) {
return filterSigned(n.GetCellular(x, y, z));
}
} }
@@ -281,6 +281,11 @@ public interface ProceduralStream<T> extends ProceduralLayer, Interpolated<T> {
return new CachedStream2D<T>(name, engine, this, size); return new CachedStream2D<T>(name, engine, this, size);
} }
@SuppressWarnings("unchecked")
default ProceduralStream<Double> cache2DDouble(String name, Engine engine, int size) {
return new CachedDoubleStream2D(name, engine, (ProceduralStream<Double>) this, size);
}
default ProceduralStream<T> cache3D(String name, Engine engine, int maxSize) { default ProceduralStream<T> cache3D(String name, Engine engine, int maxSize) {
return new CachedStream3D<T>(name, engine, this, maxSize); return new CachedStream3D<T>(name, engine, this, maxSize);
} }
@@ -0,0 +1,76 @@
package art.arcane.iris.util.project.stream.utility;
import art.arcane.iris.Iris;
import art.arcane.iris.core.service.PreservationSVC;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.MeteredCache;
import art.arcane.iris.util.project.stream.BasicStream;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.cache.WorldCache2DDouble;
import art.arcane.volmlib.util.data.KCache;
public class CachedDoubleStream2D extends BasicStream<Double> implements ProceduralStream<Double>, MeteredCache, ChunkFillableStream2D {
private final ProceduralStream<Double> stream;
private final WorldCache2DDouble cache;
private final Engine engine;
public CachedDoubleStream2D(String name, Engine engine, ProceduralStream<Double> stream, int size) {
super();
this.stream = stream;
this.engine = engine;
this.cache = new WorldCache2DDouble((x, z) -> stream.getDouble(x, z), size);
Iris.service(PreservationSVC.class).registerCache(this);
}
@Override
public double toDouble(Double t) {
return t;
}
@Override
public Double fromDouble(double d) {
return d;
}
@Override
public Double get(double x, double z) {
return cache.get((int) x, (int) z);
}
@Override
public Double get(double x, double y, double z) {
return stream.get(x, y, z);
}
@Override
public double getDouble(double x, double z) {
return cache.get((int) x, (int) z);
}
@Override
public long getSize() {
return cache.getSize();
}
@Override
public KCache<?, ?> getRawCache() {
return null;
}
@Override
public long getMaxSize() {
return cache.getMaxSize();
}
@Override
public boolean isClosed() {
return engine.isClosed();
}
@Override
public void fillChunkRaw(int worldX, int worldZ, Object[] target) {
int chunkX = worldX >> 4;
int chunkZ = worldZ >> 4;
cache.fillChunk(chunkX, chunkZ, target);
}
}
@@ -27,7 +27,7 @@ import art.arcane.volmlib.util.cache.WorldCache2D;
import art.arcane.volmlib.util.data.KCache; import art.arcane.volmlib.util.data.KCache;
import art.arcane.iris.util.project.stream.BasicStream; import art.arcane.iris.util.project.stream.BasicStream;
import art.arcane.iris.util.project.stream.ProceduralStream; import art.arcane.iris.util.project.stream.ProceduralStream;
public class CachedStream2D<T> extends BasicStream<T> implements ProceduralStream<T>, MeteredCache { public class CachedStream2D<T> extends BasicStream<T> implements ProceduralStream<T>, MeteredCache, ChunkFillableStream2D {
private final ProceduralStream<T> stream; private final ProceduralStream<T> stream;
private final WorldCache2D<T> cache; private final WorldCache2D<T> cache;
private final Engine engine; private final Engine engine;
@@ -81,7 +81,7 @@ public class CachedStream2D<T> extends BasicStream<T> implements ProceduralStrea
return engine.isClosed(); return engine.isClosed();
} }
public void fillChunk(int worldX, int worldZ, Object[] target) { public void fillChunkRaw(int worldX, int worldZ, Object[] target) {
int chunkX = worldX >> 4; int chunkX = worldX >> 4;
int chunkZ = worldZ >> 4; int chunkZ = worldZ >> 4;
cache.fillChunk(chunkX, chunkZ, target); cache.fillChunk(chunkX, chunkZ, target);
@@ -0,0 +1,5 @@
package art.arcane.iris.util.project.stream.utility;
public interface ChunkFillableStream2D {
void fillChunkRaw(int worldX, int worldZ, Object[] target);
}
@@ -0,0 +1,141 @@
package art.arcane.iris.core.pregenerator;
import art.arcane.iris.core.IrisSettings;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.math.Position2;
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
public class IrisPregeneratorInitTest {
@Test
public void initDoesNotSaveBeforeGenerationStarts() throws Exception {
IrisSettings previousSettings = IrisSettings.settings;
IrisSettings.settings = new IrisSettings();
TrackingPregeneratorMethod method = new TrackingPregeneratorMethod();
PregenTask task = PregenTask.builder()
.center(new Position2(0, 0))
.radiusX(16)
.radiusZ(16)
.build();
try {
IrisPregenerator pregenerator = new IrisPregenerator(task, method, new NoOpPregenListener());
Method initMethod = IrisPregenerator.class.getDeclaredMethod("init");
initMethod.setAccessible(true);
initMethod.invoke(pregenerator);
assertEquals(1, method.initCalls.get());
assertEquals(0, method.saveCalls.get());
} finally {
IrisSettings.settings = previousSettings;
}
}
private static final class TrackingPregeneratorMethod implements PregeneratorMethod {
private final AtomicInteger initCalls = new AtomicInteger();
private final AtomicInteger saveCalls = new AtomicInteger();
@Override
public void init() {
initCalls.incrementAndGet();
}
@Override
public void close() {
}
@Override
public void save() {
saveCalls.incrementAndGet();
}
@Override
public boolean supportsRegions(int x, int z, PregenListener listener) {
return false;
}
@Override
public String getMethod(int x, int z) {
return "test";
}
@Override
public void generateRegion(int x, int z, PregenListener listener) {
}
@Override
public void generateChunk(int x, int z, PregenListener listener) {
}
@Override
public Mantle getMantle() {
return null;
}
}
private static final class NoOpPregenListener implements PregenListener {
@Override
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
}
@Override
public void onChunkGenerating(int x, int z) {
}
@Override
public void onChunkGenerated(int x, int z, boolean cached) {
}
@Override
public void onRegionGenerated(int x, int z) {
}
@Override
public void onRegionGenerating(int x, int z) {
}
@Override
public void onChunkCleaned(int x, int z) {
}
@Override
public void onRegionSkipped(int x, int z) {
}
@Override
public void onNetworkStarted(int x, int z) {
}
@Override
public void onNetworkFailed(int x, int z) {
}
@Override
public void onNetworkReclaim(int revert) {
}
@Override
public void onNetworkGeneratedChunk(int x, int z) {
}
@Override
public void onNetworkDownloaded(int x, int z) {
}
@Override
public void onClose() {
}
@Override
public void onSaving() {
}
@Override
public void onChunkExistsInRegionGen(int x, int z) {
}
}
}
@@ -29,4 +29,16 @@ public class AsyncPregenMethodConcurrencyCapTest {
assertEquals(16, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, false, 8)); assertEquals(16, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, false, 8));
assertEquals(20, AsyncPregenMethod.applyRuntimeConcurrencyCap(20, false, 40)); assertEquals(20, AsyncPregenMethod.applyRuntimeConcurrencyCap(20, false, 40));
} }
@Test
public void paperLikeConcurrencyUsesChunkWorkerPoolWhenAvailable() {
assertEquals(4, AsyncPregenMethod.resolvePaperLikeConcurrencyWorkerThreads(4, 16, 32));
assertEquals(24, AsyncPregenMethod.resolvePaperLikeConcurrencyWorkerThreads(-1, 16, 24));
}
@Test
public void foliaConcurrencyStillUsesBroaderRuntimeCapacity() {
assertEquals(32, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(4, 16, 32));
assertEquals(16, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(-1, 16, 12));
}
} }
@@ -0,0 +1,24 @@
package art.arcane.iris.core.service;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisEngineSVCTest {
@Test
public void maintenanceSkipsReductionWhenPregenDoesNotTargetWorld() {
assertTrue(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(true, false));
}
@Test
public void maintenanceDoesNotSkipReductionForActivePregenWorld() {
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(true, true));
}
@Test
public void noMaintenanceNeverSkipsReduction() {
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(false, false));
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(false, true));
}
}
@@ -0,0 +1,67 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.util.project.interpolation.InterpolationMethod3D;
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
import art.arcane.iris.util.project.noise.HexJamesNoise;
import art.arcane.iris.util.project.noise.HexRandomSizeNoise;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.function.NoiseProvider3;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class IrisMathNoiseHotPathParityTest {
@Test
public void hexNoiseSamplesRemainBitExact() {
HexRandomSizeNoise randomSize = new HexRandomSizeNoise(12345L);
HexJamesNoise james = new HexJamesNoise(54321L);
assertEquals(0.4670864519829246D, randomSize.noise(12.5D, -8.25D), 0D);
assertEquals(0.4935590260372404D, randomSize.noise(-31.75D, 44.5D, 9.125D), 0D);
assertEquals(0.5185375921658786D, james.noise(12.5D, -8.25D), 0D);
assertEquals(0.4557738681128938D, james.noise(-31.75D, 44.5D, 9.125D), 0D);
}
@Test
public void generatorNoiseSamplesRemainBitExact() {
IrisNoiseGenerator noiseGenerator = new IrisNoiseGenerator()
.setZoom(7.25D)
.setOffsetX(1.75D)
.setOffsetZ(-3.5D)
.setOpacity(0.82D)
.setStyle(NoiseStyle.PERLIN_IRIS.style())
.setExponent(1.15D);
assertEquals(0.3886291844359823D, noiseGenerator.getNoise(9988L, 31.25D, -17.5D, null), 0D);
IrisGeneratorStyle style = NoiseStyle.IRIS_DOUBLE.style();
assertEquals(0.8640377573263068D, style.createNoCache(new RNG(112233L), null).noiseFast2D(42.5D, -19.75D), 0D);
IrisGenerator generator = new IrisGenerator()
.setZoom(9.5D)
.setOpacity(0.91D)
.setComposite(new KList<IrisNoiseGenerator>().qadd(noiseGenerator));
assertEquals(0.451949817597527D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
}
@Test
public void interpolationSamplesRemainBitExact() {
NoiseProvider3 provider = (x, y, z) -> {
double angle = (x * 0.017D) + (y * 0.011D) + (z * 0.023D);
return 0.5D + (Math.sin(angle) * 0.25D);
};
assertEquals(
0.5231950552025616D,
IrisInterpolation.getNoise3D(InterpolationMethod3D.TRILINEAR, 5, 7, -3, 2.5D, 3.5D, 4.5D, provider),
0D
);
assertEquals(
0.5259208842929466D,
IrisInterpolation.getNoise3D(InterpolationMethod3D.TRICUBIC, 5, 7, -3, 2.5D, 3.5D, 4.5D, provider),
0D
);
}
}
@@ -27,6 +27,16 @@ public class CNGFastPathParityTest {
} }
} }
@Test
public void signedFastPathMatchesLegacyAcrossRepresentativeGenerators() {
for (long seed = 31L; seed <= 35L; seed++) {
List<CNG> generators = createSignedGenerators(seed);
for (int index = 0; index < generators.size(); index++) {
assertSignedFastPathParity("signed-seed-" + seed + "-case-" + index, generators.get(index));
}
}
}
private void assertFastPathParity(String label, CNG generator) { private void assertFastPathParity(String label, CNG generator) {
for (int x = -320; x <= 320; x += 19) { for (int x = -320; x <= 320; x += 19) {
for (int z = -320; z <= 320; z += 23) { for (int z = -320; z <= 320; z += 23) {
@@ -47,6 +57,26 @@ public class CNGFastPathParityTest {
} }
} }
private void assertSignedFastPathParity(String label, CNG generator) {
for (int x = -320; x <= 320; x += 19) {
for (int z = -320; z <= 320; z += 23) {
double expected = (generator.noiseFast2D(x, z) * 2D) - 1D;
double actual = generator.noiseFastSigned2D(x, z);
assertEquals(label + " signed-2D x=" + x + " z=" + z, expected, actual, 1.0E-12D);
}
}
for (int x = -128; x <= 128; x += 17) {
for (int y = -96; y <= 96; y += 13) {
for (int z = -128; z <= 128; z += 19) {
double expected = (generator.noiseFast3D(x, y, z) * 2D) - 1D;
double actual = generator.noiseFastSigned3D(x, y, z);
assertEquals(label + " signed-3D x=" + x + " y=" + y + " z=" + z, expected, actual, 1.0E-12D);
}
}
}
}
private CNG createIdentityGenerator(long seed) { private CNG createIdentityGenerator(long seed) {
DeterministicNoiseGenerator generator = new DeterministicNoiseGenerator(0.31D + (seed * 0.01D)); DeterministicNoiseGenerator generator = new DeterministicNoiseGenerator(0.31D + (seed * 0.01D));
return new CNG(new RNG(seed), generator, 1D, 1).bake(); return new CNG(new RNG(seed), generator, 1D, 1).bake();
@@ -72,6 +102,24 @@ public class CNGFastPathParityTest {
return generators; return generators;
} }
private List<CNG> createSignedGenerators(long seed) {
List<CNG> generators = new ArrayList<>();
generators.add(new CNG(new RNG(seed), new SimplexNoise(seed + 100L), 1D, 1).bake());
generators.add(new CNG(new RNG(seed + 1L), new FractalFBMSimplexNoise(seed + 101L), 1D, 1).bake());
generators.add(new CNG(new RNG(seed + 2L), new VascularNoise(seed + 102L), 1D, 1).bake());
generators.add(new CNG(new RNG(seed + 3L), new OffsetNoiseGenerator(new SimplexNoise(seed + 103L), seed + 104L), 1D, 1).bake());
generators.add(new CNG(new RNG(seed + 4L), new OffsetNoiseGenerator(new PerlinNoise(seed + 107L), seed + 108L), 1D, 1).bake());
generators.add(new CNG(new RNG(seed + 5L), new OffsetNoiseGenerator(new VascularNoise(seed + 109L), seed + 110L), 1D, 1).bake());
generators.add(new CNG(new RNG(seed + 6L), new OffsetNoiseGenerator(new FractalFBMSimplexNoise(seed + 111L), seed + 112L), 1D, 1).bake());
CNG fractured = new CNG(new RNG(seed + 7L), new OffsetNoiseGenerator(new FractalFBMSimplexNoise(seed + 105L), seed + 106L), 1D, 1).bake();
fractured.fractureWith(createIdentityGenerator(seed + 500L), 9.5D);
generators.add(fractured);
return generators;
}
private static class DeterministicNoiseGenerator implements NoiseGenerator { private static class DeterministicNoiseGenerator implements NoiseGenerator {
private final double offset; private final double offset;
@@ -0,0 +1,183 @@
package art.arcane.iris.util.project.noise;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static org.junit.Assert.assertEquals;
public class FastNoiseDoubleCellularParityTest {
private static final FastNoiseDouble.CellularDistanceFunction[] DISTANCE_FUNCTIONS = new FastNoiseDouble.CellularDistanceFunction[]{
FastNoiseDouble.CellularDistanceFunction.Euclidean,
FastNoiseDouble.CellularDistanceFunction.Manhattan,
FastNoiseDouble.CellularDistanceFunction.Natural
};
private static final FastNoiseDouble.CellularReturnType[] TWO_EDGE_RETURN_TYPES = new FastNoiseDouble.CellularReturnType[]{
FastNoiseDouble.CellularReturnType.Distance2,
FastNoiseDouble.CellularReturnType.Distance2Add,
FastNoiseDouble.CellularReturnType.Distance2Sub,
FastNoiseDouble.CellularReturnType.Distance2Mul,
FastNoiseDouble.CellularReturnType.Distance2Div
};
private static final Method HASH_2D = getDeclaredMethod("hash2D", long.class, long.class, long.class);
private static final Method HASH_3D = getDeclaredMethod("hash3D", long.class, long.class, long.class, long.class);
private static final double[] CELL_2D_X = getDeclaredDoubleArray("CELL_2D_X");
private static final double[] CELL_2D_Y = getDeclaredDoubleArray("CELL_2D_Y");
private static final double[] CELL_3D_X = getDeclaredDoubleArray("CELL_3D_X");
private static final double[] CELL_3D_Y = getDeclaredDoubleArray("CELL_3D_Y");
private static final double[] CELL_3D_Z = getDeclaredDoubleArray("CELL_3D_Z");
@Test
public void cellular2DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception {
for (long seed = 11L; seed <= 17L; seed++) {
double frequency = 0.037D + (seed * 0.001D);
for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) {
for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) {
FastNoiseDouble noise = new FastNoiseDouble(seed);
noise.setFrequency(frequency);
noise.setCellularDistanceFunction(distanceFunction);
noise.setCellularReturnType(returnType);
for (int x = -96; x <= 96; x += 11) {
for (int y = -96; y <= 96; y += 13) {
double sampleX = x + 0.37D;
double sampleY = y - 0.41D;
double expected = legacyGetCellular2D(seed, frequency, distanceFunction, returnType, sampleX, sampleY);
double actual = noise.GetCellular(sampleX, sampleY);
assertEquals("2D seed=" + seed + " distance=" + distanceFunction + " return=" + returnType + " x=" + sampleX + " y=" + sampleY, expected, actual, 0D);
}
}
}
}
}
}
@Test
public void cellular3DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception {
for (long seed = 31L; seed <= 35L; seed++) {
double frequency = 0.029D + (seed * 0.001D);
for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) {
for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) {
FastNoiseDouble noise = new FastNoiseDouble(seed);
noise.setFrequency(frequency);
noise.setCellularDistanceFunction(distanceFunction);
noise.setCellularReturnType(returnType);
for (int x = -48; x <= 48; x += 9) {
for (int y = -32; y <= 32; y += 7) {
for (int z = -48; z <= 48; z += 11) {
double sampleX = x + 0.19D;
double sampleY = y - 0.27D;
double sampleZ = z + 0.43D;
double expected = legacyGetCellular3D(seed, frequency, distanceFunction, returnType, sampleX, sampleY, sampleZ);
double actual = noise.GetCellular(sampleX, sampleY, sampleZ);
assertEquals("3D seed=" + seed + " distance=" + distanceFunction + " return=" + returnType + " x=" + sampleX + " y=" + sampleY + " z=" + sampleZ, expected, actual, 0D);
}
}
}
}
}
}
}
private double legacyGetCellular2D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y) throws Exception {
double scaledX = x * frequency;
double scaledY = y * frequency;
long xr = fastRound(scaledX);
long yr = fastRound(scaledY);
double distance = 999999D;
double distance2 = 999999D;
for (long xi = xr - 1; xi <= xr + 1; xi++) {
for (long yi = yr - 1; yi <= yr + 1; yi++) {
int cellIndex = (int) hash2D(seed, xi, yi) & 255;
double vecX = xi - scaledX + CELL_2D_X[cellIndex];
double vecY = yi - scaledY + CELL_2D_Y[cellIndex];
double newDistance = switch (distanceFunction) {
case Euclidean -> vecX * vecX + vecY * vecY;
case Manhattan -> Math.abs(vecX) + Math.abs(vecY);
case Natural -> (Math.abs(vecX) + Math.abs(vecY)) + (vecX * vecX + vecY * vecY);
};
distance2 = Math.max(Math.min(distance2, newDistance), distance);
distance = Math.min(distance, newDistance);
}
}
return applyTwoEdgeReturn(distance, distance2, returnType);
}
private double legacyGetCellular3D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y, double z) throws Exception {
double scaledX = x * frequency;
double scaledY = y * frequency;
double scaledZ = z * frequency;
long xr = fastRound(scaledX);
long yr = fastRound(scaledY);
long zr = fastRound(scaledZ);
double distance = 999999D;
double distance2 = 999999D;
for (long xi = xr - 1; xi <= xr + 1; xi++) {
for (long yi = yr - 1; yi <= yr + 1; yi++) {
for (long zi = zr - 1; zi <= zr + 1; zi++) {
int cellIndex = (int) hash3D(seed, xi, yi, zi) & 255;
double vecX = xi - scaledX + CELL_3D_X[cellIndex];
double vecY = yi - scaledY + CELL_3D_Y[cellIndex];
double vecZ = zi - scaledZ + CELL_3D_Z[cellIndex];
double newDistance = switch (distanceFunction) {
case Euclidean -> vecX * vecX + vecY * vecY + vecZ * vecZ;
case Manhattan -> Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ);
case Natural -> (Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ);
};
distance2 = Math.max(Math.min(distance2, newDistance), distance);
distance = Math.min(distance, newDistance);
}
}
}
return applyTwoEdgeReturn(distance, distance2, returnType);
}
private double applyTwoEdgeReturn(double distance, double distance2, FastNoiseDouble.CellularReturnType returnType) {
return switch (returnType) {
case Distance2 -> distance2 - 1D;
case Distance2Add -> distance2 + distance - 1D;
case Distance2Sub -> distance2 - distance - 1D;
case Distance2Mul -> distance2 * distance - 1D;
case Distance2Div -> distance / distance2 - 1D;
default -> 0D;
};
}
private static long fastRound(double value) {
return value >= 0D ? (long) (value + 0.5D) : (long) (value - 0.5D);
}
private static long hash2D(long seed, long x, long y) throws Exception {
Long value = (Long) HASH_2D.invoke(null, seed, x, y);
return value.longValue();
}
private static long hash3D(long seed, long x, long y, long z) throws Exception {
Long value = (Long) HASH_3D.invoke(null, seed, x, y, z);
return value.longValue();
}
private static Method getDeclaredMethod(String name, Class<?>... parameterTypes) {
try {
Method method = FastNoiseDouble.class.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method;
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
private static double[] getDeclaredDoubleArray(String name) {
try {
Field field = FastNoiseDouble.class.getDeclaredField(name);
field.setAccessible(true);
return (double[]) field.get(null);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
}