diff --git a/core/plugins/Iris/cache/instance b/core/plugins/Iris/cache/instance index a226f3f54..c2362f085 100644 --- a/core/plugins/Iris/cache/instance +++ b/core/plugins/Iris/cache/instance @@ -1 +1 @@ -149256635 \ No newline at end of file +-1511497018 \ No newline at end of file diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java b/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java index 8d33e2aac..82903f819 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java @@ -195,7 +195,6 @@ public class IrisPregenerator { private void init() { generator.init(); - generator.save(); } private void shutdown() { diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java b/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java index a37073a38..1ded599f8 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java @@ -20,6 +20,7 @@ package art.arcane.iris.core.pregenerator; import art.arcane.volmlib.util.collection.KList; 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.Spiraled; import art.arcane.volmlib.util.math.Spiraler; @@ -31,8 +32,7 @@ import java.util.Comparator; @Builder @Data public class PregenTask { - private static final Position2 ZERO = new Position2(0, 0); - private static final KMap> ORDERS = new KMap<>(); + private static final KMap ORDERS = new KMap<>(); @Builder.Default private final boolean gui = false; @@ -54,16 +54,28 @@ public class PregenTask { } public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) { - for (Position2 i : ORDERS.computeIfAbsent(pull, PregenTask::computeOrder)) { - s.on(i.getX() + (xr << 5), i.getZ() + (zr << 5)); + iterateRegion(xr, zr, s, pull.getX(), pull.getZ()); + } + + 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) { - iterateRegion(xr, zr, s, new Position2(-(xr << 5), -(zr << 5))); + iterateRegion(xr, zr, s, -PowerOfTwoCoordinates.regionToChunk(xr), -PowerOfTwoCoordinates.regionToChunk(zr)); } - private static KList 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 p = new KList<>(); new Spiraler(33, 33, (x, z) -> { int xx = (x + 15); @@ -76,18 +88,30 @@ public class PregenTask { }).drain(); 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) { - var bound = bounds.region(); + Bound bound = bounds.region(); new Spiraler(bound.sizeX, bound.sizeZ, ((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) { - var bound = bounds.chunk(); + Bound bound = bounds.chunk(); iterateRegion(rX, rZ, ((x, z) -> { if (bound.check(x, z)) s.on(x, z); })); @@ -103,11 +127,11 @@ public class PregenTask { } KList cursors = new KList<>(); + Bound bound = bounds.chunk(); iterateRegions((regionX, regionZ) -> { - KList chunks = new KList<>(); - iterateChunks(regionX, regionZ, (chunkX, chunkZ) -> chunks.add(new Position2(chunkX, chunkZ))); - if (!chunks.isEmpty()) { - cursors.add(new RegionChunkCursor(regionX, regionZ, chunks)); + RegionChunkCursor cursor = new RegionChunkCursor(regionX, regionZ, bound); + if (cursor.hasNext()) { + cursors.add(cursor); } }); @@ -120,16 +144,18 @@ public class PregenTask { } hasProgress = true; - Position2 chunk = cursor.next(); - if (chunk == null) { + long chunk = cursor.next(); + if (chunk == Long.MIN_VALUE) { continue; } + int chunkX = (int) (chunk >> 32); + int chunkZ = (int) chunk; boolean shouldContinue = spiraled.on( cursor.getRegionX(), cursor.getRegionZ(), - chunk.getX(), - chunk.getZ(), + chunkX, + chunkZ, cursor.getIndex() == 1, !cursor.hasNext() ); @@ -155,8 +181,18 @@ public class PregenTask { int minX = center.getX() - radiusX; int minZ = center.getZ() - radiusZ; - chunk = new Bound(minX >> 4, minZ >> 4, Math.ceilDiv(maxX, 16), Math.ceilDiv(maxZ, 16)); - region = new Bound(minX >> 9, minZ >> 9, Math.ceilDiv(maxX, 512), Math.ceilDiv(maxZ, 512)); + chunk = new Bound( + 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() { @@ -199,28 +235,60 @@ public class PregenTask { private static final class RegionChunkCursor { private final int regionX; private final int regionZ; - private final KList chunks; - private int index; + private final Bound bound; + 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 chunks) { + private RegionChunkCursor(int regionX, int regionZ, Bound bound) { this.regionX = regionX; this.regionZ = regionZ; - this.chunks = chunks; - this.index = 0; + this.bound = bound; + 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() { - return index < chunks.size(); + return hasCurrent; } - private Position2 next() { + private long next() { if (!hasNext()) { - return null; + return Long.MIN_VALUE; } - Position2 value = chunks.get(index); - index++; - return value; + long high = (long) currentChunkX << 32; + long low = currentChunkZ & 0xFFFFFFFFL; + 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() { @@ -232,7 +300,7 @@ public class PregenTask { } private int getIndex() { - return index; + return emittedIndex; } } } diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java index f96d0e996..607118738 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java @@ -5,6 +5,7 @@ import art.arcane.volmlib.util.data.Varint; import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.documentation.RegionCoordinates; import art.arcane.volmlib.util.io.IO; +import art.arcane.volmlib.util.math.PowerOfTwoCoordinates; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockOutputStream; @@ -39,19 +40,22 @@ public class PregenCacheImpl implements PregenCache { @Override @ChunkCoordinates public boolean isChunkCached(int x, int z) { - return getPlate(x >> 10, z >> 10).isCached( - (x >> 5) & 31, - (z >> 5) & 31, - region -> region.isCached(x & 31, z & 31) + return getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 10), PowerOfTwoCoordinates.floorDivPow2(z, 10)).isCached( + PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS), + PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(z, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS), + region -> region.isCached( + PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS), + PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS) + ) ); } @Override @RegionCoordinates public boolean isRegionCached(int x, int z) { - return getPlate(x >> 5, z >> 5).isCached( - x & 31, - z & 31, + return getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.floorDivPow2(z, 5)).isCached( + PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS), + PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS), Region::isCached ); } @@ -59,19 +63,22 @@ public class PregenCacheImpl implements PregenCache { @Override @ChunkCoordinates public void cacheChunk(int x, int z) { - getPlate(x >> 10, z >> 10).cache( - (x >> 5) & 31, - (z >> 5) & 31, - region -> region.cache(x & 31, z & 31) + getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 10), PowerOfTwoCoordinates.floorDivPow2(z, 10)).cache( + PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS), + PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(z, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS), + region -> region.cache( + PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS), + PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS) + ) ); } @Override @RegionCoordinates public void cacheRegion(int x, int z) { - getPlate(x >> 5, z >> 5).cache( - x & 31, - z & 31, + getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.floorDivPow2(z, 5)).cache( + PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS), + PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS), Region::cache ); } @@ -215,7 +222,7 @@ public class PregenCacheImpl implements PregenCache { return false; } - int index = x * 32 + z; + int index = PowerOfTwoCoordinates.packLocal32(x, z); Region region = regions[index]; if (region == null) { region = new Region(); @@ -240,7 +247,7 @@ public class PregenCacheImpl implements PregenCache { return true; } - Region region = regions[x * 32 + z]; + Region region = regions[PowerOfTwoCoordinates.packLocal32(x, z)]; if (region == null) { return false; } @@ -294,7 +301,7 @@ public class PregenCacheImpl implements PregenCache { return false; } - int index = x * 32 + z; + int index = PowerOfTwoCoordinates.packLocal32(x, z); int wordIndex = index >> 6; long bit = 1L << (index & 63); boolean current = (value[wordIndex] & bit) != 0L; @@ -317,7 +324,7 @@ public class PregenCacheImpl implements PregenCache { } private boolean isCached(int x, int z) { - int index = x * 32 + z; + int index = PowerOfTwoCoordinates.packLocal32(x, z); if (count == SIZE) { return true; } diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java index 8f9767666..14543cf1a 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -35,14 +35,17 @@ import io.papermc.lib.PaperLib; import org.bukkit.Chunk; import org.bukkit.World; +import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import java.util.Locale; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -59,6 +62,9 @@ public class AsyncPregenMethod implements PregeneratorMethod { private final int effectiveWorkerThreads; private final int recommendedRuntimeConcurrencyCap; private final int configuredMaxConcurrency; + private final Method directChunkAtAsyncUrgentMethod; + private final Method directChunkAtAsyncMethod; + private final String chunkAccessMode; private final Executor executor; private final Semaphore semaphore; private final int threads; @@ -90,10 +96,16 @@ public class AsyncPregenMethod implements PregeneratorMethod { IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen(); this.runtimeSchedulerMode = IrisRuntimeSchedulerMode.resolve(pregen); 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 detectedCpuThreads = Math.max(1, Runtime.getRuntime().availableProcessors()); 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) { this.paperLikeBackendMode = IrisPaperLikeBackendMode.AUTO; this.backendMode = "folia-region"; @@ -156,11 +168,14 @@ public class AsyncPregenMethod implements PregeneratorMethod { private void unloadAndSaveAllChunks() { if (foliaRuntime) { - // Folia requires world/chunk mutations to be region-owned; periodic global unload/save is unsafe. lastUse.clear(); return; } + if (lastUse.isEmpty()) { + return; + } + try { J.sfut(() -> { if (world == null) { @@ -169,6 +184,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { } long minTime = M.ms() - 10_000; + AtomicBoolean unloaded = new AtomicBoolean(false); lastUse.entrySet().removeIf(i -> { final Chunk chunk = i.getKey(); final Long lastUseTime = i.getValue(); @@ -176,11 +192,14 @@ public class AsyncPregenMethod implements PregeneratorMethod { return true; if (lastUseTime < minTime) { chunk.unload(); + unloaded.set(true); return true; } return false; }); - world.save(); + if (unloaded.get()) { + world.save(); + } }).get(); } catch (Throwable e) { e.printStackTrace(); @@ -294,6 +313,14 @@ public class AsyncPregenMethod implements PregeneratorMethod { 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) { int normalizedWorkers = Math.max(1, workerThreads); int recommendedCap = normalizedWorkers * 4; @@ -308,6 +335,10 @@ public class AsyncPregenMethod implements PregeneratorMethod { 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) { int normalizedMaxConcurrency = Math.max(1, maxConcurrency); int recommendedCap = foliaRuntime @@ -407,6 +438,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { Iris.info("Async pregen init: world=" + world.getName() + ", mode=" + runtimeSchedulerMode.name().toLowerCase(Locale.ROOT) + ", backend=" + backendMode + + ", chunkAccess=" + chunkAccessMode + ", threads=" + threads + ", adaptiveLimit=" + adaptiveInFlightLimit.get() + ", workerPoolThreads=" + workerPoolThreads @@ -487,6 +519,96 @@ public class AsyncPregenMethod implements PregeneratorMethod { executor.generate(x, z, listener); } + private CompletableFuture 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 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 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) 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 public Mantle getMantle() { if (IrisToolbelt.isIrisWorld(world)) { @@ -547,14 +669,14 @@ public class AsyncPregenMethod implements PregeneratorMethod { @Override public void generate(int x, int z, PregenListener listener) { try { - PaperLib.getChunkAtAsync(world, x, z, true, urgent) + requestChunkAsync(x, z) .orTimeout(timeoutSeconds, TimeUnit.SECONDS) .whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable)); return; } 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) .whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable)))) { markFinished(false); @@ -598,7 +720,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { service.submit(() -> { boolean success = false; try { - Chunk i = PaperLib.getChunkAtAsync(world, x, z, true, urgent) + Chunk i = requestChunkAsync(x, z) .orTimeout(timeoutSeconds, TimeUnit.SECONDS) .exceptionally(e -> onChunkFutureFailure(x, z, e)) .get(); @@ -632,7 +754,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { private class TicketExecutor implements Executor { @Override public void generate(int x, int z, PregenListener listener) { - PaperLib.getChunkAtAsync(world, x, z, true, urgent) + requestChunkAsync(x, z) .orTimeout(timeoutSeconds, TimeUnit.SECONDS) .exceptionally(e -> onChunkFutureFailure(x, z, e)) .thenAccept(i -> { @@ -653,4 +775,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { }); } } + + private record ChunkAsyncMethodSelection(Method urgentMethod, Method standardMethod, String mode) { + } } diff --git a/core/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java b/core/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java index 3499554db..b360743a3 100644 --- a/core/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java @@ -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.VolmitSender; 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.CachedStream3D; +import art.arcane.iris.core.gui.PregeneratorJob; import lombok.Synchronized; import org.bukkit.Bukkit; import org.bukkit.World; @@ -98,7 +100,7 @@ public class IrisEngineSVC implements IrisService { double total = 0D; int count = 0; for (var cache : preservation.getCaches()) { - if (!(cache instanceof CachedStream2D)) { + if (!(cache instanceof CachedStream2D) && !(cache instanceof CachedDoubleStream2D)) { continue; } @@ -126,6 +128,7 @@ public class IrisEngineSVC implements IrisService { var type = switch (cache) { case ResourceLoader ignored -> 0; case CachedStream2D ignored -> 1; + case CachedDoubleStream2D ignored -> 1; case CachedStream3D ignored -> 2; default -> 3; }; @@ -250,6 +253,10 @@ public class IrisEngineSVC implements IrisService { return false; } + static boolean shouldSkipMantleReductionForMaintenance(boolean maintenanceActive, boolean pregeneratorTargetsWorld) { + return maintenanceActive && !pregeneratorTargetsWorld; + } + private final class Registered { private final String name; private final PlatformChunkGenerator access; @@ -286,7 +293,7 @@ public class IrisEngineSVC implements IrisService { || !shouldReduce(engine)) return; World engineWorld = engine.getWorld().realWorld(); - if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) { + if (shouldSkipForMaintenance(engineWorld)) { return; } @@ -313,7 +320,7 @@ public class IrisEngineSVC implements IrisService { || !shouldReduce(engine)) return; World engineWorld = engine.getWorld().realWorld(); - if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) { + if (shouldSkipForMaintenance(engineWorld)) { return; } @@ -363,7 +370,32 @@ public class IrisEngineSVC implements IrisService { } 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); } } } diff --git a/core/src/main/java/art/arcane/iris/engine/IrisComplex.java b/core/src/main/java/art/arcane/iris/engine/IrisComplex.java index 465d27ff2..a599781a8 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisComplex.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisComplex.java @@ -200,11 +200,11 @@ public class IrisComplex implements DataProvider { heightStream = ProceduralStream.of((x, z) -> { IrisBiome b = focusBiome != null ? focusBiome : baseBiomeStream.get(x, z); 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)) .round().waste("Rounded Height Stream"); 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, b -> focusBiome)) .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)) .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)) - .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"); 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"); diff --git a/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java b/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java index c50b09c96..bdf043636 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java @@ -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.flag.MantleFlag; 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.RNG; import art.arcane.volmlib.util.matter.Matter; @@ -219,8 +220,8 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } J.runEntity(player, () -> { - int centerX = player.getLocation().getBlockX() >> 4; - int centerZ = player.getLocation().getBlockZ() >> 4; + int centerX = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockX()); + int centerZ = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockZ()); int radius = 1; for (int x = -radius; x <= radius; x++) { for (int z = -radius; z <= radius; z++) { @@ -278,8 +279,8 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } J.runEntity(player, () -> { - int centerX = player.getLocation().getBlockX() >> 4; - int centerZ = player.getLocation().getBlockZ() >> 4; + int centerX = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockX()); + int centerZ = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockZ()); int radius = 1; for (int x = -radius; x <= radius; x++) { @@ -632,12 +633,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager { .filter((i) -> i.isValid(biome)), Stream.concat(getData() .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) .stream() .filter(filter), 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) .stream() .filter(filter))) @@ -668,13 +669,13 @@ public class IrisWorldManager extends EngineAssignedWorldManager { private void spawn(IrisPosition pos, IrisEntitySpawn i) { 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; int s = i.spawn(getEngine(), pos, RNG.r); actuallySpawned += s; 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)); } } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java index 2622fbd07..cb41778af 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java @@ -26,6 +26,7 @@ import art.arcane.iris.engine.object.IrisCaveProfile; import art.arcane.iris.engine.object.IrisRange; import art.arcane.iris.util.project.noise.CNG; 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.matter.Matter; import art.arcane.volmlib.util.matter.MatterCavern; @@ -55,8 +56,11 @@ public class IrisCaveCarver3D { private final MatterCavern carveAir; private final MatterCavern carveLava; private final MatterCavern carveForcedAir; + private final double normalizationFactor; private final double baseWeight; private final double detailWeight; + private final double detailMinContribution; + private final double detailMaxContribution; private final double warpStrength; private final boolean hasWarp; private final boolean hasModules; @@ -93,8 +97,11 @@ public class IrisCaveCarver3D { this.modules = moduleStates.toArray(new ModuleState[0]); double normalization = weight <= 0 ? 1 : weight; + normalizationFactor = normalization; inverseNormalization = 1D / normalization; hasModules = modules.length > 0; + detailMinContribution = -detailWeight; + detailMaxContribution = detailWeight; } public int carve(MantleWriter writer, int chunkX, int chunkZ) { @@ -178,8 +185,8 @@ public class IrisCaveCarver3D { return 0; } - int x0 = chunkX << 4; - int z0 = chunkZ << 4; + int x0 = PowerOfTwoCoordinates.chunkToBlock(chunkX); + int z0 = PowerOfTwoCoordinates.chunkToBlock(chunkZ); int[] columnMaxY = scratch.columnMaxY; int[] surfaceBreakFloorY = scratch.surfaceBreakFloorY; boolean[] surfaceBreakColumn = scratch.surfaceBreakColumn; @@ -193,7 +200,7 @@ public class IrisCaveCarver3D { int x = x0 + lx; for (int lz = 0; lz < 16; lz++) { int z = z0 + lz; - int index = (lx << 4) | lz; + int index = PowerOfTwoCoordinates.packLocal16(lx, lz); int columnSurfaceY; if (precomputedSurfaceHeights != null && precomputedSurfaceHeights.length > index) { columnSurfaceY = precomputedSurfaceHeights[index]; @@ -202,7 +209,7 @@ public class IrisCaveCarver3D { } int clearanceTopY = Math.min(maxY, Math.max(minY, columnSurfaceY - surfaceClearance)); boolean breakColumn = allowSurfaceBreak - && signed(surfaceBreakDensity.noiseFast2D(x, z)) >= surfaceBreakNoiseThreshold; + && surfaceBreakDensity.noiseFastSigned2D(x, z) >= surfaceBreakNoiseThreshold; int columnTopY = breakColumn ? Math.min(maxY, Math.max(minY, columnSurfaceY)) : clearanceTopY; @@ -399,13 +406,14 @@ public class IrisCaveCarver3D { } int[] planeColumnIndices = scratch.planeColumnIndices; - double[] planeDensity = scratch.planeDensity; - int minSection = minY >> 4; - int maxSection = maxY >> 4; + double[] planeThresholdLimit = scratch.planeThresholdLimit; + boolean[] planeCarve = scratch.planeCarve; + int minSection = PowerOfTwoCoordinates.floorDivPow2(minY, 4); + int maxSection = PowerOfTwoCoordinates.floorDivPow2(maxY, 4); for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) { - int sectionMinY = Math.max(minY, sectionIndex << 4); - int sectionMaxY = Math.min(maxY, (sectionIndex << 4) + 15); + int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex)); + int sectionMaxY = Math.min(maxY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex) + 15); MatterSlice cavernSlice = resolveCavernSlice(scratch, chunk, sectionIndex); for (int y = sectionMinY; y <= sectionMaxY; y++) { @@ -415,7 +423,14 @@ public class IrisCaveCarver3D { 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++; } @@ -423,24 +438,19 @@ public class IrisCaveCarver3D { continue; } - fillDensityPlane(x0, z0, y, planeColumnIndices, planeCount, planeDensity); + classifyDensityPlane(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve); int fadeIndex = y - minY; int localY = y & 15; MatterCavern matter = matterByY[fadeIndex]; if (skipExistingCarved) { for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { - int columnIndex = planeColumnIndices[planeIndex]; - double localThreshold = passThreshold[columnIndex]; - if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) { - localThreshold += surfaceBreakThresholdBoost; - } - localThreshold -= verticalEdgeFade[fadeIndex]; - if (planeDensity[planeIndex] > localThreshold) { + if (!planeCarve[planeIndex]) { continue; } - int localX = columnIndex >> 4; + int columnIndex = planeColumnIndices[planeIndex]; + int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex); int localZ = columnIndex & 15; if (cavernSlice.get(localX, localY, localZ) != null) { continue; @@ -453,17 +463,12 @@ public class IrisCaveCarver3D { } for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { - int columnIndex = planeColumnIndices[planeIndex]; - double localThreshold = passThreshold[columnIndex]; - if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) { - localThreshold += surfaceBreakThresholdBoost; - } - localThreshold -= verticalEdgeFade[fadeIndex]; - if (planeDensity[planeIndex] > localThreshold) { + if (!planeCarve[planeIndex]) { continue; } - int localX = columnIndex >> 4; + int columnIndex = planeColumnIndices[planeIndex]; + int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex); int localZ = columnIndex & 15; cavernSlice.set(localX, localY, localZ, matter); carved++; @@ -519,7 +524,7 @@ public class IrisCaveCarver3D { int lz1 = lz + 1; int activeColumns = 0; - int index00 = (lx << 4) | lz; + int index00 = PowerOfTwoCoordinates.packLocal16(lx, lz); if (!Double.isNaN(passThreshold[index00])) { tileIndices[activeColumns] = index00; tileLocalX[activeColumns] = lx; @@ -528,7 +533,7 @@ public class IrisCaveCarver3D { activeColumns++; } - int index01 = (lx << 4) | lz1; + int index01 = PowerOfTwoCoordinates.packLocal16(lx, lz1); if (!Double.isNaN(passThreshold[index01])) { tileIndices[activeColumns] = index01; tileLocalX[activeColumns] = lx; @@ -537,7 +542,7 @@ public class IrisCaveCarver3D { activeColumns++; } - int index10 = (lx1 << 4) | lz; + int index10 = PowerOfTwoCoordinates.packLocal16(lx1, lz); if (!Double.isNaN(passThreshold[index10])) { tileIndices[activeColumns] = index10; tileLocalX[activeColumns] = lx1; @@ -546,7 +551,7 @@ public class IrisCaveCarver3D { activeColumns++; } - int index11 = (lx1 << 4) | lz1; + int index11 = PowerOfTwoCoordinates.packLocal16(lx1, lz1); if (!Double.isNaN(passThreshold[index11])) { tileIndices[activeColumns] = index11; tileLocalX[activeColumns] = lx1; @@ -574,7 +579,7 @@ public class IrisCaveCarver3D { int stampMaxY = Math.min(maxY, y + 1); for (int yy = y; yy <= stampMaxY; yy++) { MatterCavern matter = matterByY[yy - minY]; - MatterSlice cavernSlice = resolveCavernSlice(scratch, chunk, yy >> 4); + MatterSlice cavernSlice = resolveCavernSlice(scratch, chunk, PowerOfTwoCoordinates.floorDivPow2(yy, 4)); int localY = yy & 15; int fadeIndex = yy - minY; for (int columnIndex = 0; columnIndex < activeColumns; columnIndex++) { @@ -640,7 +645,7 @@ public class IrisCaveCarver3D { int x = x0 + lx; for (int lz = 0; lz < 16; lz++) { int z = z0 + lz; - int index = (lx << 4) | lz; + int index = PowerOfTwoCoordinates.packLocal16(lx, lz); double columnWeight = clampedWeights[index]; if (columnWeight <= minWeight) { continue; @@ -669,7 +674,7 @@ public class IrisCaveCarver3D { int carveMaxY = Math.min(columnTopY, y + sampleStep - 1); for (int yy = y; yy <= carveMaxY; yy++) { MatterCavern matter = matterByY[yy - minY]; - MatterSlice cavernSlice = resolveCavernSlice(scratch, chunk, yy >> 4); + MatterSlice cavernSlice = resolveCavernSlice(scratch, chunk, PowerOfTwoCoordinates.floorDivPow2(yy, 4)); int localY = yy & 15; if (skipExistingCarved) { if (cavernSlice.get(lx, localY, lz) == null) { @@ -719,153 +724,234 @@ public class IrisCaveCarver3D { 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 (!hasModules) { - fillDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity); + classifyDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve); return; } - fillDensityPlaneNoWarpModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity); + classifyDensityPlaneNoWarpModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve); return; } if (!hasModules) { - fillDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeCount, planeDensity); + classifyDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve); 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 localDetailDensity = detailDensity; double localBaseWeight = baseWeight; double localDetailWeight = detailWeight; - double normalization = inverseNormalization; + double detailMin = detailMinContribution; + double detailMax = detailMaxContribution; for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { int columnIndex = planeColumnIndices[planeIndex]; - int x = x0 + (columnIndex >> 4); + int x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex); int z = z0 + (columnIndex & 15); - double density = signed(localBaseDensity.noiseFast3D(x, y, z)) * localBaseWeight; - density += signed(localDetailDensity.noiseFast3D(x, y, z)) * localDetailWeight; - planeDensity[planeIndex] = density * normalization; - } - } - - private void fillDensityPlaneNoWarpModules(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) { - CNG localBaseDensity = baseDensity; - CNG localDetailDensity = detailDensity; - 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; + double thresholdLimit = planeThresholdLimit[planeIndex]; + double density = localBaseDensity.noiseFastSigned3D(x, y, z) * localBaseWeight; + if ((density + detailMin) > thresholdLimit) { + planeCarve[planeIndex] = false; + continue; + } + if ((density + detailMax) <= thresholdLimit) { + planeCarve[planeIndex] = true; + continue; } - 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 localDetailDensity = detailDensity; CNG localWarpDensity = warpDensity; double localBaseWeight = baseWeight; double localDetailWeight = detailWeight; double localWarpStrength = warpStrength; - double normalization = inverseNormalization; + double detailMin = detailMinContribution; + double detailMax = detailMaxContribution; for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { int columnIndex = planeColumnIndices[planeIndex]; - double x = x0 + (columnIndex >> 4); + double x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex); double z = z0 + (columnIndex & 15); - double warpA = signed(localWarpDensity.noiseFast3D(x, y, z)); - double warpB = signed(localWarpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); + double thresholdLimit = planeThresholdLimit[planeIndex]; + 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 warpedY = y + (warpB * localWarpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength); - double density = signed(localBaseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localBaseWeight; - density += signed(localDetailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localDetailWeight; - planeDensity[planeIndex] = density * normalization; + double density = localBaseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localBaseWeight; + if ((density + detailMin) > thresholdLimit) { + 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 localDetailDensity = detailDensity; 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 localDetailWeight = detailWeight; double localWarpStrength = warpStrength; - double normalization = inverseNormalization; + double detailMin = detailMinContribution; + double detailMax = detailMaxContribution; for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { int columnIndex = planeColumnIndices[planeIndex]; - double x = x0 + (columnIndex >> 4); + double x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex); double z = z0 + (columnIndex & 15); - double warpA = signed(localWarpDensity.noiseFast3D(x, y, z)); - double warpB = signed(localWarpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); + double thresholdLimit = planeThresholdLimit[planeIndex]; + 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 warpedY = y + (warpB * localWarpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength); - double density = signed(localBaseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localBaseWeight; - density += signed(localDetailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * 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(warpedX, warpedY, warpedZ)) - module.threshold; - if (module.invert) { - moduleDensity = -moduleDensity; - } - - density += moduleDensity * module.weight; + double density = localBaseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localBaseWeight; + if ((density + detailMin + remainingMin[0]) > thresholdLimit) { + planeCarve[planeIndex] = false; + continue; + } + if ((density + detailMax + remainingMax[0]) <= thresholdLimit) { + planeCarve[planeIndex] = true; + continue; } - 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) { - double density = signed(baseDensity.noiseFast3D(x, y, z)) * baseWeight; - density += signed(detailDensity.noiseFast3D(x, y, z)) * detailWeight; + double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight; + density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight; return density * inverseNormalization; } private double sampleDensityNoWarpModules(int x, int y, int z) { - double density = signed(baseDensity.noiseFast3D(x, y, z)) * baseWeight; - density += signed(detailDensity.noiseFast3D(x, y, z)) * detailWeight; - for (int moduleIndex = 0; moduleIndex < modules.length; moduleIndex++) { - ModuleState module = modules[moduleIndex]; - if (y < module.minY || y > module.maxY) { - continue; - } + Scratch scratch = SCRATCH.get(); + int activeModuleCount = prepareActiveModules(scratch, y); + if (activeModuleCount == 0) { + return sampleDensityNoWarpNoModules(x, y, z); + } - 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) { moduleDensity = -moduleDensity; } @@ -877,31 +963,34 @@ public class IrisCaveCarver3D { } private double sampleDensityWarpOnly(int x, int y, int z) { - double warpA = signed(warpDensity.noiseFast3D(x, y, z)); - double warpB = signed(warpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); + 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 warpedY = y + (warpB * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); - double density = signed(baseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * baseWeight; - density += signed(detailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * detailWeight; + double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight; + density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight; return density * inverseNormalization; } private double sampleDensityWarpModules(int x, int y, int z) { - double warpA = signed(warpDensity.noiseFast3D(x, y, z)); - double warpB = signed(warpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D)); + Scratch scratch = SCRATCH.get(); + 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 warpedY = y + (warpB * warpStrength); double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength); - double density = signed(baseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * baseWeight; - density += signed(detailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * detailWeight; - for (int moduleIndex = 0; moduleIndex < modules.length; moduleIndex++) { - ModuleState module = modules[moduleIndex]; - if (y < module.minY || y > module.maxY) { - continue; - } - - double moduleDensity = signed(module.density.noiseFast3D(warpedX, warpedY, warpedZ)) - module.threshold; + double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight; + density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight; + for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) { + ModuleState module = localModules[moduleIndex]; + double moduleDensity = module.density.noiseFastSigned3D(warpedX, warpedY, warpedZ) - module.threshold; if (module.invert) { moduleDensity = -moduleDensity; } @@ -912,6 +1001,44 @@ public class IrisCaveCarver3D { 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 resolveCavernSlice(Scratch scratch, MantleChunk chunk, int sectionIndex) { @SuppressWarnings("unchecked") MatterSlice cachedSlice = (MatterSlice) scratch.sectionSlices[sectionIndex]; @@ -964,8 +1091,8 @@ public class IrisCaveCarver3D { } private void prepareSectionCaches(Scratch scratch, int minY, int maxY) { - int minSection = Math.max(0, minY >> 4); - int maxSection = Math.max(minSection, maxY >> 4); + int minSection = Math.max(0, PowerOfTwoCoordinates.floorDivPow2(minY, 4)); + int maxSection = Math.max(minSection, PowerOfTwoCoordinates.floorDivPow2(maxY, 4)); int requiredSections = maxSection + 1; if (scratch.sectionMatter.length < requiredSections) { scratch.sectionMatter = new Matter[requiredSections]; @@ -1038,6 +1165,8 @@ public class IrisCaveCarver3D { private final double weight; private final double threshold; private final boolean invert; + private final double minContribution; + private final double maxContribution; private ModuleState(IrisCaveFieldModule module, CNG density) { IrisRange range = module.getVerticalRange(); @@ -1047,6 +1176,20 @@ public class IrisCaveCarver3D { this.weight = module.getWeight(); this.threshold = module.getThreshold(); 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[] activeColumnTopY = 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[] tileLocalX = new int[4]; private final int[] tileLocalZ = 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 MatterCavern[] matterByY = new MatterCavern[0]; private Matter[] sectionMatter = new Matter[0]; diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java index 35e536ab8..99a87b1e8 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java @@ -33,6 +33,7 @@ import art.arcane.iris.engine.object.IrisRange; import art.arcane.iris.util.project.context.ChunkContext; import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.mantle.flag.ReservedFlag; +import art.arcane.volmlib.util.math.PowerOfTwoCoordinates; import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -159,7 +160,7 @@ public class MantleCarvingComponent extends IrisMantleComponent { continue; } - int columnIndex = (localX << 4) | localZ; + int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ); double dominantWeight = clampWeight(dominantKernelWeight / totalKernelWeight); double[] weights = columnProfileWeights.get(dominantProfile); 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) { - int baseX = chunkX << 4; - int baseZ = chunkZ << 4; + int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX); + int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ); for (int localX = 0; localX < CHUNK_SIZE; localX++) { int worldX = baseX + localX; for (int localZ = 0; localZ < CHUNK_SIZE; 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); } } } private void fillProfileField(IrisCaveProfile[] profileField, int chunkX, int chunkZ, IrisComplex complex, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap caveBiomeCache) { - int startX = (chunkX << 4) - BLEND_RADIUS; - int startZ = (chunkZ << 4) - BLEND_RADIUS; + int startX = PowerOfTwoCoordinates.chunkToBlock(chunkX) - BLEND_RADIUS; + int startZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ) - BLEND_RADIUS; for (int fieldX = 0; fieldX < FIELD_SIZE; 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) { int[] surfaceHeights = scratch; - int baseX = chunkX << 4; - int baseZ = chunkZ << 4; + int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX); + int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ); boolean useContextHeight = context != null && context.getHeight() != null && context.getX() == baseX @@ -366,7 +367,7 @@ public class MantleCarvingComponent extends IrisMantleComponent { int worldX = baseX + localX; for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) { int worldZ = baseZ + localZ; - int columnIndex = (localX << 4) | localZ; + int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ); if (useContextHeight) { Double cachedHeight = context.getHeight().get(localX, localZ); if (cachedHeight != null) { diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java index 39c5fadbe..14b4700aa 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java @@ -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.math.BlockPosition; 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.matter.Matter; import art.arcane.volmlib.util.matter.MatterCavern; @@ -89,7 +90,7 @@ public class IrisCarveModifier extends EngineAssignedModifier { int rx = xx & 15; int rz = zz & 15; - int columnIndex = (rx << 4) | rz; + int columnIndex = PowerOfTwoCoordinates.packLocal16(rx, rz); BlockData current = output.get(rx, yy, rz); if (B.isFluid(current)) { @@ -135,8 +136,8 @@ public class IrisCarveModifier extends EngineAssignedModifier { PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start(); try { walls.forEach((rx, yy, rz, cavern) -> { - int worldX = rx + (x << 4); - int worldZ = rz + (z << 4); + int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(x); + int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(z); String customBiome = cavern.getCustomBiome(); IrisBiome biome = customBiome.isEmpty() ? resolveCaveBiome(caveBiomeCache, worldX, yy, worldZ, resolverState) @@ -184,10 +185,10 @@ public class IrisCarveModifier extends EngineAssignedModifier { return; } - int rx = columnIndex >> 4; + int rx = PowerOfTwoCoordinates.unpackLocal16X(columnIndex); int rz = columnIndex & 15; - int worldX = rx + (chunkX << 4); - int worldZ = rz + (chunkZ << 4); + int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX); + int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ); CaveZone zone = new CaveZone(); zone.setFloor(firstHeight); int buf = firstHeight - 1; @@ -239,7 +240,7 @@ public class IrisCarveModifier extends EngineAssignedModifier { } 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); if (cavernData != null && !cavernData.getCustomBiome().isEmpty()) { @@ -447,11 +448,11 @@ public class IrisCarveModifier extends EngineAssignedModifier { } 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) { - return (key >> 4) & 15; + return PowerOfTwoCoordinates.unpackLocal16X(key & 255); } private int unpackY(int key) { @@ -459,7 +460,7 @@ public class IrisCarveModifier extends EngineAssignedModifier { } private int unpackZ(int key) { - return key & 15; + return PowerOfTwoCoordinates.unpackLocal16Z(key); } private int mix(int value) { diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java b/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java index 9f210b75c..c416dee93 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java @@ -179,14 +179,17 @@ public class IrisBiome extends IrisRegistrant implements IRare { if (ores.isEmpty()) { return null; } - BlockData b = null; - for (IrisOreGenerator i : ores) { - if (i.isGenerateSurface() != surface) + KList localOres = ores; + int oreCount = localOres.size(); + for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) { + IrisOreGenerator oreGenerator = localOres.get(oreIndex); + if (oreGenerator.isGenerateSurface() != surface) { continue; + } - b = i.generate(x, y, z, rng, data); - if (b != null) { - return b; + BlockData ore = oreGenerator.generate(x, y, z, rng, data); + if (ore != null) { + return ore; } } return null; diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisBiomePaletteLayer.java b/core/src/main/java/art/arcane/iris/engine/object/IrisBiomePaletteLayer.java index 91967e6ee..11992e8f6 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisBiomePaletteLayer.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisBiomePaletteLayer.java @@ -77,7 +77,10 @@ public class IrisBiomePaletteLayer { 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) { diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java index 369351397..7da5b9e41 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java @@ -290,22 +290,25 @@ public class IrisDimension extends IrisRegistrant { carvingEntryIndex.reset(); } - public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) { - if (ores.isEmpty()) { - return null; - } - BlockData b = null; - for (IrisOreGenerator i : ores) { - if (i.isGenerateSurface() != surface) - continue; - - b = i.generate(x, y, z, rng, data); - if (b != null) { - return b; - } - } - return null; - } + public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) { + if (ores.isEmpty()) { + return null; + } + KList localOres = ores; + int oreCount = localOres.size(); + for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) { + IrisOreGenerator oreGenerator = localOres.get(oreIndex); + if (oreGenerator.isGenerateSurface() != surface) { + continue; + } + + BlockData ore = oreGenerator.generate(x, y, z, rng, data); + if (ore != null) { + return ore; + } + } + return null; + } public int getFluidHeight() { return fluidHeight - (int) dimensionHeight.getMin(); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisGenerator.java b/core/src/main/java/art/arcane/iris/engine/object/IrisGenerator.java index fd50a0196..23a063d76 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisGenerator.java @@ -212,21 +212,23 @@ public class IrisGenerator extends IrisRegistrant { int hc = (int) ((cliffHeightMin * 10) + 10 + cliffHeightMax * getSeed() + offsetX + offsetZ); double h = multiplicitive ? 1 : 0; double tp = 0; + double sampleX = (rx + offsetX) / zoom; + double sampleZ = (rz + offsetZ) / zoom; if (composite.size() == 1) { 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 { 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 { for (IrisNoiseGenerator i : composite) { 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 { 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) { 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() { @@ -254,7 +258,9 @@ public class IrisGenerator extends IrisRegistrant { public double getCliffHeight(double rx, double rz, double superSeed) { 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); } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java b/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java index 21e88a0cc..adb6a0f45 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java @@ -33,6 +33,7 @@ import lombok.experimental.Accessors; import java.io.File; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; @Snippet("style") @Accessors(chain = true) @@ -41,6 +42,7 @@ import java.util.Objects; @Desc("A gen style") @Data public class IrisGeneratorStyle { + private static final ConcurrentHashMap ACTIVE_CACHE_KEYS = new ConcurrentHashMap<>(); private final transient AtomicCache cng = new AtomicCache<>(); @Desc("The chance is 1 in CHANCE per interval") private NoiseStyle style = NoiseStyle.FLAT; @@ -127,6 +129,11 @@ public class IrisGeneratorStyle { private void clearStaleCacheEntries(IrisData data, String prefix, String key) { 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)); if (files == null) { return; diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java b/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java index d1de0ed0a..73b737880 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java @@ -61,7 +61,10 @@ public class IrisMaterialPalette { 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 getTile(RNG rng, double x, double y, double z, IrisData rdata) { diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisNoiseGenerator.java b/core/src/main/java/art/arcane/iris/engine/object/IrisNoiseGenerator.java index 29e5f9b98..1224c582c 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisNoiseGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisNoiseGenerator.java @@ -22,8 +22,8 @@ import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.engine.data.cache.AtomicCache; import art.arcane.iris.engine.object.annotations.*; 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.iris.util.project.interpolation.IrisInterpolation; import art.arcane.iris.util.project.noise.CNG; import lombok.AllArgsConstructor; import lombok.Data; @@ -100,14 +100,17 @@ public class IrisNoiseGenerator { for (IrisNoiseGenerator i : fracture) { if (i.isEnabled()) { - x += i.getNoise(superSeed + seed + g, xv, zv, data) - (i.getOpacity() / 2D); - z += i.getNoise(superSeed + seed + g, zv, xv, data) - (i.getOpacity() / 2D); + double fractureOffset = 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; } 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 = (exponent != 1 ? n < 0 ? -Math.pow(-n, exponent) : Math.pow(n, exponent) : n) + offsetY; n = parametric ? IrisInterpolation.parametric(n, 1) : n; diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java index 3b2ea4a75..c3baf230a 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java @@ -155,14 +155,17 @@ public class IrisRegion extends IrisRegistrant implements IRare { if (ores.isEmpty()) { return null; } - BlockData b = null; - for (IrisOreGenerator i : ores) { - if (i.isGenerateSurface() != surface) + KList localOres = ores; + int oreCount = localOres.size(); + for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) { + IrisOreGenerator oreGenerator = localOres.get(oreIndex); + if (oreGenerator.isGenerateSurface() != surface) { continue; + } - b = i.generate(x, y, z, rng, data); - if (b != null) { - return b; + BlockData ore = oreGenerator.generate(x, y, z, rng, data); + if (ore != null) { + return ore; } } return null; diff --git a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java index c911f987e..03458157c 100644 --- a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java @@ -21,6 +21,7 @@ package art.arcane.iris.engine.platform; import art.arcane.iris.Iris; import art.arcane.iris.core.IrisSettings; 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.nms.INMS; import art.arcane.iris.core.service.StudioSVC; @@ -81,6 +82,8 @@ import java.util.concurrent.locks.ReentrantLock; @Data public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener { 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 IrisWorld world; private final File dataLocation; @@ -563,11 +566,15 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun this.hotloader = studio ? new Looper() { @Override protected long loop() { + if (shouldThrottleHotload()) { + return HOTLOAD_MAINTENANCE_DELAY_MS; + } + if (hotloadChecker.flip()) { folder.check(); } - return 250; + return HOTLOAD_LOOP_DELAY_MS; } } : null; @@ -735,10 +742,24 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun return true; } + return isMaintenanceActive(); + } + + private boolean isMaintenanceActive() { World realWorld = this.world.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 public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) { Engine currentEngine = engine; diff --git a/core/src/main/java/art/arcane/iris/util/common/data/BiomeMap.java b/core/src/main/java/art/arcane/iris/util/common/data/BiomeMap.java index 0afba971d..a8dd10136 100644 --- a/core/src/main/java/art/arcane/iris/util/common/data/BiomeMap.java +++ b/core/src/main/java/art/arcane/iris/util/common/data/BiomeMap.java @@ -19,6 +19,7 @@ package art.arcane.iris.util.common.data; import art.arcane.iris.engine.object.IrisBiome; +import art.arcane.volmlib.util.math.PowerOfTwoCoordinates; public class BiomeMap { private final IrisBiome[] height; @@ -28,10 +29,10 @@ public class BiomeMap { } 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) { - return height[x * 16 + z]; + return height[PowerOfTwoCoordinates.packLocal16(x, z)]; } } diff --git a/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDataCache.java b/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDataCache.java index 0d180c973..2c578e75e 100644 --- a/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDataCache.java +++ b/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDataCache.java @@ -1,7 +1,7 @@ package art.arcane.iris.util.project.context; 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 java.util.concurrent.Executor; @@ -35,8 +35,8 @@ public class ChunkedDataCache { return; } - if (stream instanceof CachedStream2D cachedStream) { - cachedStream.fillChunk(x, z, data); + if (stream instanceof ChunkFillableStream2D cachedStream) { + cachedStream.fillChunkRaw(x, z, data); return; } diff --git a/core/src/main/java/art/arcane/iris/util/project/interpolation/IrisInterpolation.java b/core/src/main/java/art/arcane/iris/util/project/interpolation/IrisInterpolation.java index 443f0d4d6..bbbd82c86 100644 --- a/core/src/main/java/art/arcane/iris/util/project/interpolation/IrisInterpolation.java +++ b/core/src/main/java/art/arcane/iris/util/project/interpolation/IrisInterpolation.java @@ -32,7 +32,6 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; import java.math.BigDecimal; import java.util.Arrays; -import java.util.HashMap; public class IrisInterpolation { public static CNG cng = NoiseStyle.SIMPLEX.create(new RNG()); @@ -952,18 +951,10 @@ public class IrisInterpolation { */ public static Hunk getNoise3D(InterpolationMethod3D method, int xo, int yo, int zo, int w, int h, int d, double radX, double radY, double radZ, NoiseProvider3 n) { Hunk hunk = Hunk.newAtomicDoubleHunk(w, h, d); - HashMap cache = new HashMap<>(); - int i, j, k; - - for (i = 0; i < w; i++) { - 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))); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + for (int k = 0; k < d; k++) { + hunk.set(i, j, k, getNoise3D(method, i + xo, j + yo, k + zo, radX, radY, radZ, n)); } } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java b/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java index 2471d1efa..05ddf52e9 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java @@ -74,6 +74,10 @@ public class CNG { private NoiseStyle leakStyle; private ProceduralStream customGenerator; 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; public CNG(RNG random) { @@ -635,7 +639,8 @@ public class CNG { } private double getNoise(double... dim) { - double scale = noscale ? 1 : this.bakedScale * this.scale; + ensureFastPathState(); + double scale = effectiveScale; if (fracture == null || noscale) { return generator.noise( @@ -671,7 +676,8 @@ public class CNG { } private double getNoise(double x) { - double scl = noscale ? 1 : this.bakedScale * this.scale; + ensureFastPathState(); + double scl = effectiveScale; if (fracture == null || noscale) { return generator.noise(x * scl, 0D, 0D) * opacity; @@ -682,7 +688,8 @@ public class CNG { } private double getNoise(double x, double z) { - double scl = noscale ? 1 : this.bakedScale * this.scale; + ensureFastPathState(); + double scl = effectiveScale; if (fracture == null || noscale) { 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) { - double scl = noscale ? 1 : this.bakedScale * this.scale; + ensureFastPathState(); + double scl = effectiveScale; if (fracture == null || noscale) { return generator.noise(x * scl, y * scl, z * scl) * opacity; @@ -932,6 +940,18 @@ public class CNG { 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) { 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); } + 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) { this.power = power; markFastPathStateDirty(); @@ -964,11 +992,19 @@ public class CNG { } private boolean isIdentityPostFastPath() { + ensureFastPathState(); + return identityPostFastPath; + } + + private boolean hasIdentitySignedFastPath() { + ensureFastPathState(); + return identitySignedFastPath; + } + + private void ensureFastPathState() { if (fastPathStateDirty) { refreshFastPathState(); } - - return identityPostFastPath; } private void markFastPathStateDirty() { @@ -976,15 +1012,62 @@ public class CNG { } private void refreshFastPathState() { + effectiveScale = noscale ? 1D : bakedScale * scale; identityPostFastPath = power == 1D && children == null && fracture == null && down == 0D && up == 0D && patch == 1D; + identitySignedFastPath = power == 1D + && children == null + && down == 0D + && up == 0D + && patch == 1D; + unityOpacity = opacity == 1D; + signedFractureScale = 0.5D * fscale; 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 { ADD, SRC_SUBTRACT, diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/FastNoiseDouble.java b/core/src/main/java/art/arcane/iris/util/project/noise/FastNoiseDouble.java index 34970d77b..a6c3cb636 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/FastNoiseDouble.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/FastNoiseDouble.java @@ -21,14 +21,22 @@ import art.arcane.volmlib.util.math.Vector2f; import art.arcane.volmlib.util.math.Vector3f; public class FastNoiseDouble { - private static final Double2[] GRAD_2D = {new Double2(-1, -1), new Double2(1, -1), new Double2(-1, 1), new Double2(1, 1), new Double2(0, -1), new Double2(-1, 0), new Double2(0, 1), new Double2(1, 0), - }; - private static final Double3[] GRAD_3D = {new Double3(1, 1, 0), new Double3(-1, 1, 0), new Double3(1, -1, 0), new Double3(-1, -1, 0), new Double3(1, 0, 1), new Double3(-1, 0, 1), new Double3(1, 0, -1), new Double3(-1, 0, -1), new Double3(0, 1, 1), new Double3(0, -1, 1), new Double3(0, 1, -1), new Double3(0, -1, -1), new Double3(1, 1, 0), new Double3(0, -1, 1), new Double3(-1, 1, 0), new Double3(0, -1, -1), + private static final double[] GRAD_2D = {-1D, -1D, 1D, -1D, -1D, 1D, 1D, 1D, 0D, -1D, -1D, 0D, 0D, 1D, 1D, 0D}; + private static final double[] GRAD_3D = { + 1D, 1D, 0D, -1D, 1D, 0D, 1D, -1D, 0D, -1D, -1D, 0D, + 1D, 0D, 1D, -1D, 0D, 1D, 1D, 0D, -1D, -1D, 0D, -1D, + 0D, 1D, 1D, 0D, -1D, 1D, 0D, 1D, -1D, 0D, -1D, -1D, + 1D, 1D, 0D, 0D, -1D, 1D, -1D, 1D, 0D, 0D, -1D, -1D }; private static final Double2[] CELL_2D = {new Double2(-0.4313539279f, 0.1281943404f), new Double2(-0.1733316799f, 0.415278375f), new Double2(-0.2821957395f, -0.3505218461f), new Double2(-0.2806473808f, 0.3517627718f), new Double2(0.3125508975f, -0.3237467165f), new Double2(0.3383018443f, -0.2967353402f), new Double2(-0.4393982022f, -0.09710417025f), new Double2(-0.4460443703f, -0.05953502905f), new Double2(-0.302223039f, 0.3334085102f), new Double2(-0.212681052f, -0.3965687458f), new Double2(-0.2991156529f, 0.3361990872f), new Double2(0.2293323691f, 0.3871778202f), new Double2(0.4475439151f, -0.04695150755f), new Double2(0.1777518f, 0.41340573f), new Double2(0.1688522499f, -0.4171197882f), new Double2(-0.0976597166f, 0.4392750616f), new Double2(0.08450188373f, 0.4419948321f), new Double2(-0.4098760448f, -0.1857461384f), new Double2(0.3476585782f, -0.2857157906f), new Double2(-0.3350670039f, -0.30038326f), new Double2(0.2298190031f, -0.3868891648f), new Double2(-0.01069924099f, 0.449872789f), new Double2(-0.4460141246f, -0.05976119672f), new Double2(0.3650293864f, 0.2631606867f), new Double2(-0.349479423f, 0.2834856838f), new Double2(-0.4122720642f, 0.1803655873f), new Double2(-0.267327811f, 0.3619887311f), new Double2(0.322124041f, -0.3142230135f), new Double2(0.2880445931f, -0.3457315612f), new Double2(0.3892170926f, -0.2258540565f), new Double2(0.4492085018f, -0.02667811596f), new Double2(-0.4497724772f, 0.01430799601f), new Double2(0.1278175387f, -0.4314657307f), new Double2(-0.03572100503f, 0.4485799926f), new Double2(-0.4297407068f, -0.1335025276f), new Double2(-0.3217817723f, 0.3145735065f), new Double2(-0.3057158873f, 0.3302087162f), new Double2(-0.414503978f, 0.1751754899f), new Double2(-0.3738139881f, 0.2505256519f), new Double2(0.2236891408f, -0.3904653228f), new Double2(0.002967775577f, -0.4499902136f), new Double2(0.1747128327f, -0.4146991995f), new Double2(-0.4423772489f, -0.08247647938f), new Double2(-0.2763960987f, -0.355112935f), new Double2(-0.4019385906f, -0.2023496216f), new Double2(0.3871414161f, -0.2293938184f), new Double2(-0.430008727f, 0.1326367019f), new Double2(-0.03037574274f, -0.4489736231f), new Double2(-0.3486181573f, 0.2845441624f), new Double2(0.04553517144f, -0.4476902368f), new Double2(-0.0375802926f, 0.4484280562f), new Double2(0.3266408905f, 0.3095250049f), new Double2(0.06540017593f, -0.4452222108f), new Double2(0.03409025829f, 0.448706869f), new Double2(-0.4449193635f, 0.06742966669f), new Double2(-0.4255936157f, -0.1461850686f), new Double2(0.449917292f, 0.008627302568f), new Double2(0.05242606404f, 0.4469356864f), new Double2(-0.4495305179f, -0.02055026661f), new Double2(-0.1204775703f, 0.4335725488f), new Double2(-0.341986385f, -0.2924813028f), new Double2(0.3865320182f, 0.2304191809f), new Double2(0.04506097811f, -0.447738214f), new Double2(-0.06283465979f, 0.4455915232f), new Double2(0.3932600341f, -0.2187385324f), new Double2(0.4472261803f, -0.04988730975f), new Double2(0.3753571011f, -0.2482076684f), new Double2(-0.273662295f, 0.357223947f), new Double2(0.1700461538f, 0.4166344988f), new Double2(0.4102692229f, 0.1848760794f), new Double2(0.323227187f, -0.3130881435f), new Double2(-0.2882310238f, -0.3455761521f), new Double2(0.2050972664f, 0.4005435199f), new Double2(0.4414085979f, -0.08751256895f), new Double2(-0.1684700334f, 0.4172743077f), new Double2(-0.003978032396f, 0.4499824166f), new Double2(-0.2055133639f, 0.4003301853f), new Double2(-0.006095674897f, -0.4499587123f), new Double2(-0.1196228124f, -0.4338091548f), new Double2(0.3901528491f, -0.2242337048f), new Double2(0.01723531752f, 0.4496698165f), new Double2(-0.3015070339f, 0.3340561458f), new Double2(-0.01514262423f, -0.4497451511f), new Double2(-0.4142574071f, -0.1757577897f), new Double2(-0.1916377265f, -0.4071547394f), new Double2(0.3749248747f, 0.2488600778f), new Double2(-0.2237774255f, 0.3904147331f), new Double2(-0.4166343106f, -0.1700466149f), new Double2(0.3619171625f, 0.267424695f), new Double2(0.1891126846f, -0.4083336779f), new Double2(-0.3127425077f, 0.323561623f), new Double2(-0.3281807787f, 0.307891826f), new Double2(-0.2294806661f, 0.3870899429f), new Double2(-0.3445266136f, 0.2894847362f), new Double2(-0.4167095422f, -0.1698621719f), new Double2(-0.257890321f, -0.3687717212f), new Double2(-0.3612037825f, 0.2683874578f), new Double2(0.2267996491f, 0.3886668486f), new Double2(0.207157062f, 0.3994821043f), new Double2(0.08355176718f, -0.4421754202f), new Double2(-0.4312233307f, 0.1286329626f), new Double2(0.3257055497f, 0.3105090899f), new Double2(0.177701095f, -0.4134275279f), new Double2(-0.445182522f, 0.06566979625f), new Double2(0.3955143435f, 0.2146355146f), new Double2(-0.4264613988f, 0.1436338239f), new Double2(-0.3793799665f, -0.2420141339f), new Double2(0.04617599081f, -0.4476245948f), new Double2(-0.371405428f, -0.2540826796f), new Double2(0.2563570295f, -0.3698392535f), new Double2(0.03476646309f, 0.4486549822f), new Double2(-0.3065454405f, 0.3294387544f), new Double2(-0.2256979823f, 0.3893076172f), new Double2(0.4116448463f, -0.1817925206f), new Double2(-0.2907745828f, -0.3434387019f), new Double2(0.2842278468f, -0.348876097f), new Double2(0.3114589359f, -0.3247973695f), new Double2(0.4464155859f, -0.0566844308f), new Double2(-0.3037334033f, -0.3320331606f), new Double2(0.4079607166f, 0.1899159123f), new Double2(-0.3486948919f, -0.2844501228f), new Double2(0.3264821436f, 0.3096924441f), new Double2(0.3211142406f, 0.3152548881f), new Double2(0.01183382662f, 0.4498443737f), new Double2(0.4333844092f, 0.1211526057f), new Double2(0.3118668416f, 0.324405723f), new Double2(-0.272753471f, 0.3579183483f), new Double2(-0.422228622f, -0.1556373694f), new Double2(-0.1009700099f, -0.4385260051f), new Double2(-0.2741171231f, -0.3568750521f), new Double2(-0.1465125133f, 0.4254810025f), new Double2(0.2302279044f, -0.3866459777f), new Double2(-0.3699435608f, 0.2562064828f), new Double2(0.105700352f, -0.4374099171f), new Double2(-0.2646713633f, 0.3639355292f), new Double2(0.3521828122f, 0.2801200935f), new Double2(-0.1864187807f, -0.4095705534f), new Double2(0.1994492955f, -0.4033856449f), new Double2(0.3937065066f, 0.2179339044f), new Double2(-0.3226158377f, 0.3137180602f), new Double2(0.3796235338f, 0.2416318948f), new Double2(0.1482921929f, 0.4248640083f), new Double2(-0.407400394f, 0.1911149365f), new Double2(0.4212853031f, 0.1581729856f), new Double2(-0.2621297173f, 0.3657704353f), new Double2(-0.2536986953f, -0.3716678248f), new Double2(-0.2100236383f, 0.3979825013f), new Double2(0.3624152444f, 0.2667493029f), new Double2(-0.3645038479f, -0.2638881295f), new Double2(0.2318486784f, 0.3856762766f), new Double2(-0.3260457004f, 0.3101519002f), new Double2(-0.2130045332f, -0.3963950918f), new Double2(0.3814998766f, -0.2386584257f), new Double2(-0.342977305f, 0.2913186713f), new Double2(-0.4355865605f, 0.1129794154f), new Double2(-0.2104679605f, 0.3977477059f), new Double2(0.3348364681f, -0.3006402163f), new Double2(0.3430468811f, 0.2912367377f), new Double2(-0.2291836801f, -0.3872658529f), new Double2(0.2547707298f, -0.3709337882f), new Double2(0.4236174945f, -0.151816397f), new Double2(-0.15387742f, 0.4228731957f), new Double2(-0.4407449312f, 0.09079595574f), new Double2(-0.06805276192f, -0.444824484f), new Double2(0.4453517192f, -0.06451237284f), new Double2(0.2562464609f, -0.3699158705f), new Double2(0.3278198355f, -0.3082761026f), new Double2(-0.4122774207f, -0.1803533432f), new Double2(0.3354090914f, -0.3000012356f), new Double2(0.446632869f, -0.05494615882f), new Double2(-0.1608953296f, 0.4202531296f), new Double2(-0.09463954939f, 0.4399356268f), new Double2(-0.02637688324f, -0.4492262904f), new Double2(0.447102804f, -0.05098119915f), new Double2(-0.4365670908f, 0.1091291678f), new Double2(-0.3959858651f, 0.2137643437f), new Double2(-0.4240048207f, -0.1507312575f), new Double2(-0.3882794568f, 0.2274622243f), new Double2(-0.4283652566f, -0.1378521198f), new Double2(0.3303888091f, 0.305521251f), new Double2(0.3321434919f, -0.3036127481f), new Double2(-0.413021046f, -0.1786438231f), new Double2(0.08403060337f, -0.4420846725f), new Double2(-0.3822882919f, 0.2373934748f), new Double2(-0.3712395594f, -0.2543249683f), new Double2(0.4472363971f, -0.04979563372f), new Double2(-0.4466591209f, 0.05473234629f), new Double2(0.0486272539f, -0.4473649407f), new Double2(-0.4203101295f, -0.1607463688f), new Double2(0.2205360833f, 0.39225481f), new Double2(-0.3624900666f, 0.2666476169f), new Double2(-0.4036086833f, -0.1989975647f), new Double2(0.2152727807f, 0.3951678503f), new Double2(-0.4359392962f, -0.1116106179f), new Double2(0.4178354266f, 0.1670735057f), new Double2(0.2007630161f, 0.4027334247f), new Double2(-0.07278067175f, -0.4440754146f), new Double2(0.3644748615f, -0.2639281632f), new Double2(-0.4317451775f, 0.126870413f), new Double2(-0.297436456f, 0.3376855855f), new Double2(-0.2998672222f, 0.3355289094f), new Double2(-0.2673674124f, 0.3619594822f), new Double2(0.2808423357f, 0.3516071423f), new Double2(0.3498946567f, 0.2829730186f), new Double2(-0.2229685561f, 0.390877248f), new Double2(0.3305823267f, 0.3053118493f), new Double2(-0.2436681211f, -0.3783197679f), new Double2(-0.03402776529f, 0.4487116125f), new Double2(-0.319358823f, 0.3170330301f), new Double2(0.4454633477f, -0.06373700535f), new Double2(0.4483504221f, 0.03849544189f), new Double2(-0.4427358436f, -0.08052932871f), new Double2(0.05452298565f, 0.4466847255f), new Double2(-0.2812560807f, 0.3512762688f), new Double2(0.1266696921f, 0.4318041097f), new Double2(-0.3735981243f, 0.2508474468f), new Double2(0.2959708351f, -0.3389708908f), new Double2(-0.3714377181f, 0.254035473f), new Double2(-0.404467102f, -0.1972469604f), new Double2(0.1636165687f, -0.419201167f), new Double2(0.3289185495f, -0.3071035458f), new Double2(-0.2494824991f, -0.3745109914f), new Double2(0.03283133272f, 0.4488007393f), new Double2(-0.166306057f, -0.4181414777f), new Double2(-0.106833179f, 0.4371346153f), new Double2(0.06440260376f, -0.4453676062f), new Double2(-0.4483230967f, 0.03881238203f), new Double2(-0.421377757f, -0.1579265206f), new Double2(0.05097920662f, -0.4471030312f), new Double2(0.2050584153f, -0.4005634111f), new Double2(0.4178098529f, -0.167137449f), new Double2(-0.3565189504f, -0.2745801121f), new Double2(0.4478398129f, 0.04403977727f), new Double2(-0.3399999602f, -0.2947881053f), new Double2(0.3767121994f, 0.2461461331f), new Double2(-0.3138934434f, 0.3224451987f), new Double2(-0.1462001792f, -0.4255884251f), new Double2(0.3970290489f, -0.2118205239f), new Double2(0.4459149305f, -0.06049689889f), new Double2(-0.4104889426f, -0.1843877112f), new Double2(0.1475103971f, -0.4251360756f), new Double2(0.09258030352f, 0.4403735771f), new Double2(-0.1589664637f, -0.4209865359f), new Double2(0.2482445008f, 0.3753327428f), new Double2(0.4383624232f, -0.1016778537f), new Double2(0.06242802956f, 0.4456486745f), new Double2(0.2846591015f, -0.3485243118f), new Double2(-0.344202744f, -0.2898697484f), new Double2(0.1198188883f, -0.4337550392f), new Double2(-0.243590703f, 0.3783696201f), new Double2(0.2958191174f, -0.3391033025f), new Double2(-0.1164007991f, 0.4346847754f), new Double2(0.1274037151f, -0.4315881062f), new Double2(0.368047306f, 0.2589231171f), new Double2(0.2451436949f, 0.3773652989f), new Double2(-0.4314509715f, 0.12786735f), }; private static final Double3[] CELL_3D = {new Double3(0.1453787434f, -0.4149781685f, -0.0956981749f), new Double3(-0.01242829687f, -0.1457918398f, -0.4255470325f), new Double3(0.2877979582f, -0.02606483451f, -0.3449535616f), new Double3(-0.07732986802f, 0.2377094325f, 0.3741848704f), new Double3(0.1107205875f, -0.3552302079f, -0.2530858567f), new Double3(0.2755209141f, 0.2640521179f, -0.238463215f), new Double3(0.294168941f, 0.1526064594f, 0.3044271714f), new Double3(0.4000921098f, -0.2034056362f, 0.03244149937f), new Double3(-0.1697304074f, 0.3970864695f, -0.1265461359f), new Double3(-0.1483224484f, -0.3859694688f, 0.1775613147f), new Double3(0.2623596946f, -0.2354852944f, 0.2796677792f), new Double3(-0.2709003183f, 0.3505271138f, -0.07901746678f), new Double3(-0.03516550699f, 0.3885234328f, 0.2243054374f), new Double3(-0.1267712655f, 0.1920044036f, 0.3867342179f), new Double3(0.02952021915f, 0.4409685861f, 0.08470692262f), new Double3(-0.2806854217f, -0.266996757f, 0.2289725438f), new Double3(-0.171159547f, 0.2141185563f, 0.3568720405f), new Double3(0.2113227183f, 0.3902405947f, -0.07453178509f), new Double3(-0.1024352839f, 0.2128044156f, -0.3830421561f), new Double3(-0.3304249877f, -0.1566986703f, 0.2622305365f), new Double3(0.2091111325f, 0.3133278055f, -0.2461670583f), new Double3(0.344678154f, -0.1944240454f, -0.2142341261f), new Double3(0.1984478035f, -0.3214342325f, -0.2445373252f), new Double3(-0.2929008603f, 0.2262915116f, 0.2559320961f), new Double3(-0.1617332831f, 0.006314769776f, -0.4198838754f), new Double3(-0.3582060271f, -0.148303178f, -0.2284613961f), new Double3(-0.1852067326f, -0.3454119342f, -0.2211087107f), new Double3(0.3046301062f, 0.1026310383f, 0.314908508f), new Double3(-0.03816768434f, -0.2551766358f, -0.3686842991f), new Double3(-0.4084952196f, 0.1805950793f, 0.05492788837f), new Double3(-0.02687443361f, -0.2749741471f, 0.3551999201f), new Double3(-0.03801098351f, 0.3277859044f, 0.3059600725f), new Double3(0.2371120802f, 0.2900386767f, -0.2493099024f), new Double3(0.4447660503f, 0.03946930643f, 0.05590469027f), new Double3(0.01985147278f, -0.01503183293f, -0.4493105419f), new Double3(0.4274339143f, 0.03345994256f, -0.1366772882f), new Double3(-0.2072988631f, 0.2871414597f, -0.2776273824f), new Double3(-0.3791240978f, 0.1281177671f, 0.2057929936f), new Double3(-0.2098721267f, -0.1007087278f, -0.3851122467f), new Double3(0.01582798878f, 0.4263894424f, 0.1429738373f), new Double3(-0.1888129464f, -0.3160996813f, -0.2587096108f), new Double3(0.1612988974f, -0.1974805082f, -0.3707885038f), new Double3(-0.08974491322f, 0.229148752f, -0.3767448739f), new Double3(0.07041229526f, 0.4150230285f, -0.1590534329f), new Double3(-0.1082925611f, -0.1586061639f, 0.4069604477f), new Double3(0.2474100658f, -0.3309414609f, 0.1782302128f), new Double3(-0.1068836661f, -0.2701644537f, -0.3436379634f), new Double3(0.2396452163f, 0.06803600538f, -0.3747549496f), new Double3(-0.3063886072f, 0.2597428179f, 0.2028785103f), new Double3(0.1593342891f, -0.3114350249f, -0.2830561951f), new Double3(0.2709690528f, 0.1412648683f, -0.3303331794f), new Double3(-0.1519780427f, 0.3623355133f, 0.2193527988f), new Double3(0.1699773681f, 0.3456012883f, 0.2327390037f), new Double3(-0.1986155616f, 0.3836276443f, -0.1260225743f), new Double3(-0.1887482106f, -0.2050154888f, -0.353330953f), new Double3(0.2659103394f, 0.3015631259f, -0.2021172246f), new Double3(-0.08838976154f, -0.4288819642f, -0.1036702021f), new Double3(-0.04201869311f, 0.3099592485f, 0.3235115047f), new Double3(-0.3230334656f, 0.201549922f, -0.2398478873f), new Double3(0.2612720941f, 0.2759854499f, -0.2409749453f), new Double3(0.385713046f, 0.2193460345f, 0.07491837764f), new Double3(0.07654967953f, 0.3721732183f, 0.241095919f), new Double3(0.4317038818f, -0.02577753072f, 0.1243675091f), new Double3(-0.2890436293f, -0.3418179959f, -0.04598084447f), new Double3(-0.2201947582f, 0.383023377f, -0.08548310451f), new Double3(0.4161322773f, -0.1669634289f, -0.03817251927f), new Double3(0.2204718095f, 0.02654238946f, -0.391391981f), new Double3(-0.1040307469f, 0.3890079625f, -0.2008741118f), new Double3(-0.1432122615f, 0.371614387f, -0.2095065525f), new Double3(0.3978380468f, -0.06206669342f, 0.2009293758f), new Double3(-0.2599274663f, 0.2616724959f, -0.2578084893f), new Double3(0.4032618332f, -0.1124593585f, 0.1650235939f), new Double3(-0.08953470255f, -0.3048244735f, 0.3186935478f), new Double3(0.118937202f, -0.2875221847f, 0.325092195f), new Double3(0.02167047076f, -0.03284630549f, -0.4482761547f), new Double3(-0.3411343612f, 0.2500031105f, 0.1537068389f), new Double3(0.3162964612f, 0.3082064153f, -0.08640228117f), new Double3(0.2355138889f, -0.3439334267f, -0.1695376245f), new Double3(-0.02874541518f, -0.3955933019f, 0.2125550295f), new Double3(-0.2461455173f, 0.02020282325f, -0.3761704803f), new Double3(0.04208029445f, -0.4470439576f, 0.02968078139f), new Double3(0.2727458746f, 0.2288471896f, -0.2752065618f), new Double3(-0.1347522818f, -0.02720848277f, -0.4284874806f), new Double3(0.3829624424f, 0.1231931484f, -0.2016512234f), new Double3(-0.3547613644f, 0.1271702173f, 0.2459107769f), new Double3(0.2305790207f, 0.3063895591f, 0.2354968222f), new Double3(-0.08323845599f, -0.1922245118f, 0.3982726409f), new Double3(0.2993663085f, -0.2619918095f, -0.2103333191f), new Double3(-0.2154865723f, 0.2706747713f, 0.287751117f), new Double3(0.01683355354f, -0.2680655787f, -0.3610505186f), new Double3(0.05240429123f, 0.4335128183f, -0.1087217856f), new Double3(0.00940104872f, -0.4472890582f, 0.04841609928f), new Double3(0.3465688735f, 0.01141914583f, -0.2868093776f), new Double3(-0.3706867948f, -0.2551104378f, 0.003156692623f), new Double3(0.2741169781f, 0.2139972417f, -0.2855959784f), new Double3(0.06413433865f, 0.1708718512f, 0.4113266307f), new Double3(-0.388187972f, -0.03973280434f, -0.2241236325f), new Double3(0.06419469312f, -0.2803682491f, 0.3460819069f), new Double3(-0.1986120739f, -0.3391173584f, 0.2192091725f), new Double3(-0.203203009f, -0.3871641506f, 0.1063600375f), new Double3(-0.1389736354f, -0.2775901578f, -0.3257760473f), new Double3(-0.06555641638f, 0.342253257f, -0.2847192729f), new Double3(-0.2529246486f, -0.2904227915f, 0.2327739768f), new Double3(0.1444476522f, 0.1069184044f, 0.4125570634f), new Double3(-0.3643780054f, -0.2447099973f, -0.09922543227f), new Double3(0.4286142488f, -0.1358496089f, -0.01829506817f), new Double3(0.165872923f, -0.3136808464f, -0.2767498872f), new Double3(0.2219610524f, -0.3658139958f, 0.1393320198f), new Double3(0.04322940318f, -0.3832730794f, 0.2318037215f), new Double3(-0.08481269795f, -0.4404869674f, -0.03574965489f), new Double3(0.1822082075f, -0.3953259299f, 0.1140946023f), new Double3(-0.3269323334f, 0.3036542563f, 0.05838957105f), new Double3(-0.4080485344f, 0.04227858267f, -0.184956522f), new Double3(0.2676025294f, -0.01299671652f, 0.36155217f), new Double3(0.3024892441f, -0.1009990293f, -0.3174892964f), new Double3(0.1448494052f, 0.425921681f, -0.0104580805f), new Double3(0.4198402157f, 0.08062320474f, 0.1404780841f), new Double3(-0.3008872161f, -0.333040905f, -0.03241355801f), new Double3(0.3639310428f, -0.1291284382f, -0.2310412139f), new Double3(0.3295806598f, 0.0184175994f, -0.3058388149f), new Double3(0.2776259487f, -0.2974929052f, -0.1921504723f), new Double3(0.4149000507f, -0.144793182f, -0.09691688386f), new Double3(0.145016715f, -0.0398992945f, 0.4241205002f), new Double3(0.09299023471f, -0.299732164f, -0.3225111565f), new Double3(0.1028907093f, -0.361266869f, 0.247789732f), new Double3(0.2683057049f, -0.07076041213f, -0.3542668666f), new Double3(-0.4227307273f, -0.07933161816f, -0.1323073187f), new Double3(-0.1781224702f, 0.1806857196f, -0.3716517945f), new Double3(0.4390788626f, -0.02841848598f, -0.09435116353f), new Double3(0.2972583585f, 0.2382799621f, -0.2394997452f), new Double3(-0.1707002821f, 0.2215845691f, 0.3525077196f), new Double3(0.3806686614f, 0.1471852559f, -0.1895464869f), new Double3(-0.1751445661f, -0.274887877f, 0.3102596268f), new Double3(-0.2227237566f, -0.2316778837f, 0.3149912482f), new Double3(0.1369633021f, 0.1341343041f, -0.4071228836f), new Double3(-0.3529503428f, -0.2472893463f, -0.129514612f), new Double3(-0.2590744185f, -0.2985577559f, -0.2150435121f), new Double3(-0.3784019401f, 0.2199816631f, -0.1044989934f), new Double3(-0.05635805671f, 0.1485737441f, 0.4210102279f), new Double3(0.3251428613f, 0.09666046873f, -0.2957006485f), new Double3(-0.4190995804f, 0.1406751354f, -0.08405978803f), new Double3(-0.3253150961f, -0.3080335042f, -0.04225456877f), new Double3(0.2857945863f, -0.05796152095f, 0.3427271751f), new Double3(-0.2733604046f, 0.1973770973f, -0.2980207554f), new Double3(0.219003657f, 0.2410037886f, -0.3105713639f), new Double3(0.3182767252f, -0.271342949f, 0.1660509868f), new Double3(-0.03222023115f, -0.3331161506f, -0.300824678f), new Double3(-0.3087780231f, 0.1992794134f, -0.2596995338f), new Double3(-0.06487611647f, -0.4311322747f, 0.1114273361f), new Double3(0.3921171432f, -0.06294284106f, -0.2116183942f), new Double3(-0.1606404506f, -0.358928121f, -0.2187812825f), new Double3(-0.03767771199f, -0.2290351443f, 0.3855169162f), new Double3(0.1394866832f, -0.3602213994f, 0.2308332918f), new Double3(-0.4345093872f, 0.005751117145f, 0.1169124335f), new Double3(-0.1044637494f, 0.4168128432f, -0.1336202785f), new Double3(0.2658727501f, 0.2551943237f, 0.2582393035f), new Double3(0.2051461999f, 0.1975390727f, 0.3484154868f), new Double3(-0.266085566f, 0.23483312f, 0.2766800993f), new Double3(0.07849405464f, -0.3300346342f, -0.2956616708f), new Double3(-0.2160686338f, 0.05376451292f, -0.3910546287f), new Double3(-0.185779186f, 0.2148499206f, 0.3490352499f), new Double3(0.02492421743f, -0.3229954284f, -0.3123343347f), new Double3(-0.120167831f, 0.4017266681f, 0.1633259825f), new Double3(-0.02160084693f, -0.06885389554f, 0.4441762538f), new Double3(0.2597670064f, 0.3096300784f, 0.1978643903f), new Double3(-0.1611553854f, -0.09823036005f, 0.4085091653f), new Double3(-0.3278896792f, 0.1461670309f, 0.2713366126f), new Double3(0.2822734956f, 0.03754421121f, -0.3484423997f), new Double3(0.03169341113f, 0.347405252f, -0.2842624114f), new Double3(0.2202613604f, -0.3460788041f, -0.1849713341f), new Double3(0.2933396046f, 0.3031973659f, 0.1565989581f), new Double3(-0.3194922995f, 0.2453752201f, -0.200538455f), new Double3(-0.3441586045f, -0.1698856132f, -0.2349334659f), new Double3(0.2703645948f, -0.3574277231f, 0.04060059933f), new Double3(0.2298568861f, 0.3744156221f, 0.0973588921f), new Double3(0.09326603877f, -0.3170108894f, 0.3054595587f), new Double3(-0.1116165319f, -0.2985018719f, 0.3177080142f), new Double3(0.2172907365f, -0.3460005203f, -0.1885958001f), new Double3(0.1991339479f, 0.3820341668f, -0.1299829458f), new Double3(-0.0541918155f, -0.2103145071f, 0.39412061f), new Double3(0.08871336998f, 0.2012117383f, 0.3926114802f), new Double3(0.2787673278f, 0.3505404674f, 0.04370535101f), new Double3(-0.322166438f, 0.3067213525f, 0.06804996813f), new Double3(-0.4277366384f, 0.132066775f, 0.04582286686f), new Double3(0.240131882f, -0.1612516055f, 0.344723946f), new Double3(0.1448607981f, -0.2387819045f, 0.3528435224f), new Double3(-0.3837065682f, -0.2206398454f, 0.08116235683f), new Double3(-0.4382627882f, -0.09082753406f, -0.04664855374f), new Double3(-0.37728353f, 0.05445141085f, 0.2391488697f), new Double3(0.1259579313f, 0.348394558f, 0.2554522098f), new Double3(-0.1406285511f, -0.270877371f, -0.3306796947f), new Double3(-0.1580694418f, 0.4162931958f, -0.06491553533f), new Double3(0.2477612106f, -0.2927867412f, -0.2353514536f), new Double3(0.2916132853f, 0.3312535401f, 0.08793624968f), new Double3(0.07365265219f, -0.1666159848f, 0.411478311f), new Double3(-0.26126526f, -0.2422237692f, 0.2748965434f), new Double3(-0.3721862032f, 0.252790166f, 0.008634938242f), new Double3(-0.3691191571f, -0.255281188f, 0.03290232422f), new Double3(0.2278441737f, -0.3358364886f, 0.1944244981f), new Double3(0.363398169f, -0.2310190248f, 0.1306597909f), new Double3(-0.304231482f, -0.2698452035f, 0.1926830856f), new Double3(-0.3199312232f, 0.316332536f, -0.008816977938f), new Double3(0.2874852279f, 0.1642275508f, -0.304764754f), new Double3(-0.1451096801f, 0.3277541114f, -0.2720669462f), new Double3(0.3220090754f, 0.0511344108f, 0.3101538769f), new Double3(-0.1247400865f, -0.04333605335f, -0.4301882115f), new Double3(-0.2829555867f, -0.3056190617f, -0.1703910946f), new Double3(0.1069384374f, 0.3491024667f, -0.2630430352f), new Double3(-0.1420661144f, -0.3055376754f, -0.2982682484f), new Double3(-0.250548338f, 0.3156466809f, -0.2002316239f), new Double3(0.3265787872f, 0.1871229129f, 0.2466400438f), new Double3(0.07646097258f, -0.3026690852f, 0.324106687f), new Double3(0.3451771584f, 0.2757120714f, -0.0856480183f), new Double3(0.298137964f, 0.2852657134f, 0.179547284f), new Double3(0.2812250376f, 0.3466716415f, 0.05684409612f), new Double3(0.4390345476f, -0.09790429955f, -0.01278335452f), new Double3(0.2148373234f, 0.1850172527f, 0.3494474791f), new Double3(0.2595421179f, -0.07946825393f, 0.3589187731f), new Double3(0.3182823114f, -0.307355516f, -0.08203022006f), new Double3(-0.4089859285f, -0.04647718411f, 0.1818526372f), new Double3(-0.2826749061f, 0.07417482322f, 0.3421885344f), new Double3(0.3483864637f, 0.225442246f, -0.1740766085f), new Double3(-0.3226415069f, -0.1420585388f, -0.2796816575f), new Double3(0.4330734858f, -0.118868561f, -0.02859407492f), new Double3(-0.08717822568f, -0.3909896417f, -0.2050050172f), new Double3(-0.2149678299f, 0.3939973956f, -0.03247898316f), new Double3(-0.2687330705f, 0.322686276f, -0.1617284888f), new Double3(0.2105665099f, -0.1961317136f, -0.3459683451f), new Double3(0.4361845915f, -0.1105517485f, 0.004616608544f), new Double3(0.05333333359f, -0.313639498f, -0.3182543336f), new Double3(-0.05986216652f, 0.1361029153f, -0.4247264031f), new Double3(0.3664988455f, 0.2550543014f, -0.05590974511f), new Double3(-0.2341015558f, -0.182405731f, 0.3382670703f), new Double3(-0.04730947785f, -0.4222150243f, -0.1483114513f), new Double3(-0.2391566239f, -0.2577696514f, -0.2808182972f), new Double3(-0.1242081035f, 0.4256953395f, -0.07652336246f), new Double3(0.2614832715f, -0.3650179274f, 0.02980623099f), new Double3(-0.2728794681f, -0.3499628774f, 0.07458404908f), new Double3(0.007892900508f, -0.1672771315f, 0.4176793787f), new Double3(-0.01730330376f, 0.2978486637f, -0.3368779738f), new Double3(0.2054835762f, -0.3252600376f, -0.2334146693f), new Double3(-0.3231994983f, 0.1564282844f, -0.2712420987f), new Double3(-0.2669545963f, 0.2599343665f, -0.2523278991f), new Double3(-0.05554372779f, 0.3170813944f, -0.3144428146f), new Double3(-0.2083935713f, -0.310922837f, -0.2497981362f), new Double3(0.06989323478f, -0.3156141536f, 0.3130537363f), new Double3(0.3847566193f, -0.1605309138f, -0.1693876312f), new Double3(-0.3026215288f, -0.3001537679f, -0.1443188342f), new Double3(0.3450735512f, 0.08611519592f, 0.2756962409f), new Double3(0.1814473292f, -0.2788782453f, -0.3029914042f), new Double3(-0.03855010448f, 0.09795110726f, 0.4375151083f), new Double3(0.3533670318f, 0.2665752752f, 0.08105160988f), new Double3(-0.007945601311f, 0.140359426f, -0.4274764309f), new Double3(0.4063099273f, -0.1491768253f, -0.1231199324f), new Double3(-0.2016773589f, 0.008816271194f, -0.4021797064f), new Double3(-0.07527055435f, -0.425643481f, -0.1251477955f), }; + private static final double[] CELL_2D_X = extractCell2DComponent(true); + private static final double[] CELL_2D_Y = extractCell2DComponent(false); + private static final double[] CELL_3D_X = extractCell3DComponent(0); + private static final double[] CELL_3D_Y = extractCell3DComponent(1); + private static final double[] CELL_3D_Z = extractCell3DComponent(2); // Hashing private final static long X_PRIME = 1619; private final static long Y_PRIME = 31337; @@ -98,6 +106,24 @@ public class FastNoiseDouble { return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b; } + private static double[] extractCell2DComponent(boolean xComponent) { + double[] components = new double[CELL_2D.length]; + for (int i = 0; i < CELL_2D.length; i++) { + Double2 value = CELL_2D[i]; + components[i] = xComponent ? value.x : value.y; + } + return components; + } + + private static double[] extractCell3DComponent(int axis) { + double[] components = new double[CELL_3D.length]; + for (int i = 0; i < CELL_3D.length; i++) { + Double3 value = CELL_3D[i]; + components[i] = axis == 0 ? value.x : axis == 1 ? value.y : value.z; + } + return components; + } + private static long hash2D(long seed, long x, long y) { long hash = seed; hash ^= X_PRIME * x; @@ -169,9 +195,8 @@ public class FastNoiseDouble { hash = hash * hash * hash * 60493; hash = (hash >> 13) ^ hash; - Double2 g = GRAD_2D[(int) hash & 7]; - - return xd * g.x + yd * g.y; + int gradientIndex = ((int) hash & 7) << 1; + return (xd * GRAD_2D[gradientIndex]) + (yd * GRAD_2D[gradientIndex + 1]); } private static double GradCoord3D(long seed, long x, long y, long z, double xd, double yd, double zd) { @@ -183,9 +208,8 @@ public class FastNoiseDouble { hash = hash * hash * hash * 60493; hash = (hash >> 13) ^ hash; - Double3 g = GRAD_3D[(int) (hash & 15)]; - - return xd * g.x + yd * g.y + zd * g.z; + int gradientIndex = ((int) (hash & 15)) * 3; + return (xd * GRAD_3D[gradientIndex]) + (yd * GRAD_3D[gradientIndex + 1]) + (zd * GRAD_3D[gradientIndex + 2]); } private static double GradCoord4D(long seed, long x, long y, long z, long w, double xd, double yd, double zd, double wd) { @@ -1457,6 +1481,10 @@ public class FastNoiseDouble { long xr = fastRound(x); long yr = fastRound(y); long zr = fastRound(z); + long seed = m_seed; + double[] cell3dX = CELL_3D_X; + double[] cell3dY = CELL_3D_Y; + double[] cell3dZ = CELL_3D_Z; double distance = 999999; long xc = 0, yc = 0, zc = 0; @@ -1464,13 +1492,15 @@ public class FastNoiseDouble { switch (m_cellularDistanceFunction) { case Euclidean: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { + double baseVecY = yi - y; for (long zi = zr - 1; zi <= zr + 1; zi++) { - Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; - double vecZ = zi - z + vec.z; + double baseVecZ = zi - z; + int cellIndex3D = (int) hash3D(seed, xi, yi, zi) & 255; + double vecX = baseVecX + cell3dX[cellIndex3D]; + double vecY = baseVecY + cell3dY[cellIndex3D]; + double vecZ = baseVecZ + cell3dZ[cellIndex3D]; double newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; @@ -1486,13 +1516,15 @@ public class FastNoiseDouble { break; case Manhattan: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { + double baseVecY = yi - y; for (long zi = zr - 1; zi <= zr + 1; zi++) { - Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; - double vecZ = zi - z + vec.z; + double baseVecZ = zi - z; + int cellIndex3D = (int) hash3D(seed, xi, yi, zi) & 255; + double vecX = baseVecX + cell3dX[cellIndex3D]; + double vecY = baseVecY + cell3dY[cellIndex3D]; + double vecZ = baseVecZ + cell3dZ[cellIndex3D]; double newDistance = Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ); @@ -1508,13 +1540,15 @@ public class FastNoiseDouble { break; case Natural: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { + double baseVecY = yi - y; for (long zi = zr - 1; zi <= zr + 1; zi++) { - Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; - double vecZ = zi - z + vec.z; + double baseVecZ = zi - z; + int cellIndex3D = (int) hash3D(seed, xi, yi, zi) & 255; + double vecX = baseVecX + cell3dX[cellIndex3D]; + double vecY = baseVecY + cell3dY[cellIndex3D]; + double vecZ = baseVecZ + cell3dZ[cellIndex3D]; double newDistance = (Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ); @@ -1535,8 +1569,8 @@ public class FastNoiseDouble { return valCoord3D(0, xc, yc, zc); case NoiseLookup: - Double3 vec = CELL_3D[(int) hash3D(m_seed, xc, yc, zc) & 255]; - return m_cellularNoiseLookup.GetNoise(xc + vec.x, yc + vec.y, zc + vec.z); + int lookupIndex3D = (int) hash3D(m_seed, xc, yc, zc) & 255; + return m_cellularNoiseLookup.GetNoise(xc + CELL_3D_X[lookupIndex3D], yc + CELL_3D_Y[lookupIndex3D], zc + CELL_3D_Z[lookupIndex3D]); case Distance: return distance - 1; @@ -1549,6 +1583,10 @@ public class FastNoiseDouble { long xr = fastRound(x); long yr = fastRound(y); long zr = fastRound(z); + long seed = m_seed; + double[] cell3dX = CELL_3D_X; + double[] cell3dY = CELL_3D_Y; + double[] cell3dZ = CELL_3D_Z; double distance = 999999; double distance2 = 999999; @@ -1556,54 +1594,72 @@ public class FastNoiseDouble { switch (m_cellularDistanceFunction) { case Euclidean: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { + double baseVecY = yi - y; for (long zi = zr - 1; zi <= zr + 1; zi++) { - Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; - double vecZ = zi - z + vec.z; + double baseVecZ = zi - z; + int cellIndex3D = (int) hash3D(seed, xi, yi, zi) & 255; + double vecX = baseVecX + cell3dX[cellIndex3D]; + double vecY = baseVecY + cell3dY[cellIndex3D]; + double vecZ = baseVecZ + cell3dZ[cellIndex3D]; double newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ; - distance2 = Math.max(Math.min(distance2, newDistance), distance); - distance = Math.min(distance, newDistance); + if (newDistance < distance) { + distance2 = distance; + distance = newDistance; + } else if (newDistance < distance2) { + distance2 = newDistance; + } } } } break; case Manhattan: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { + double baseVecY = yi - y; for (long zi = zr - 1; zi <= zr + 1; zi++) { - Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; - double vecZ = zi - z + vec.z; + double baseVecZ = zi - z; + int cellIndex3D = (int) hash3D(seed, xi, yi, zi) & 255; + double vecX = baseVecX + cell3dX[cellIndex3D]; + double vecY = baseVecY + cell3dY[cellIndex3D]; + double vecZ = baseVecZ + cell3dZ[cellIndex3D]; double newDistance = Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ); - distance2 = Math.max(Math.min(distance2, newDistance), distance); - distance = Math.min(distance, newDistance); + if (newDistance < distance) { + distance2 = distance; + distance = newDistance; + } else if (newDistance < distance2) { + distance2 = newDistance; + } } } } break; case Natural: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { + double baseVecY = yi - y; for (long zi = zr - 1; zi <= zr + 1; zi++) { - Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; - double vecZ = zi - z + vec.z; + double baseVecZ = zi - z; + int cellIndex3D = (int) hash3D(seed, xi, yi, zi) & 255; + double vecX = baseVecX + cell3dX[cellIndex3D]; + double vecY = baseVecY + cell3dY[cellIndex3D]; + double vecZ = baseVecZ + cell3dZ[cellIndex3D]; double newDistance = (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); + if (newDistance < distance) { + distance2 = distance; + distance = newDistance; + } else if (newDistance < distance2) { + distance2 = newDistance; + } } } } @@ -1635,6 +1691,9 @@ public class FastNoiseDouble { private double SingleCellular(double x, double y) { long xr = fastRound(x); long yr = fastRound(y); + long seed = m_seed; + double[] cell2dX = CELL_2D_X; + double[] cell2dY = CELL_2D_Y; double distance = 999999; long xc = 0, yc = 0; @@ -1643,11 +1702,12 @@ public class FastNoiseDouble { default: case Euclidean: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { - Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; + double baseVecY = yi - y; + int cellIndex2D = (int) hash2D(seed, xi, yi) & 255; + double vecX = baseVecX + cell2dX[cellIndex2D]; + double vecY = baseVecY + cell2dY[cellIndex2D]; double newDistance = vecX * vecX + vecY * vecY; @@ -1661,11 +1721,12 @@ public class FastNoiseDouble { break; case Manhattan: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { - Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; + double baseVecY = yi - y; + int cellIndex2D = (int) hash2D(seed, xi, yi) & 255; + double vecX = baseVecX + cell2dX[cellIndex2D]; + double vecY = baseVecY + cell2dY[cellIndex2D]; double newDistance = (Math.abs(vecX) + Math.abs(vecY)); @@ -1679,11 +1740,12 @@ public class FastNoiseDouble { break; case Natural: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { - Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; + double baseVecY = yi - y; + int cellIndex2D = (int) hash2D(seed, xi, yi) & 255; + double vecX = baseVecX + cell2dX[cellIndex2D]; + double vecY = baseVecY + cell2dY[cellIndex2D]; double newDistance = (Math.abs(vecX) + Math.abs(vecY)) + (vecX * vecX + vecY * vecY); @@ -1702,8 +1764,8 @@ public class FastNoiseDouble { return valCoord2D(0, xc, yc); case NoiseLookup: - Double2 vec = CELL_2D[(int) hash2D(m_seed, xc, yc) & 255]; - return m_cellularNoiseLookup.GetNoise(xc + vec.x, yc + vec.y); + int lookupIndex2D = (int) hash2D(m_seed, xc, yc) & 255; + return m_cellularNoiseLookup.GetNoise(xc + CELL_2D_X[lookupIndex2D], yc + CELL_2D_Y[lookupIndex2D]); case Distance: return distance - 1; @@ -1715,6 +1777,9 @@ public class FastNoiseDouble { private double SingleCellular2Edge(double x, double y) { long xr = fastRound(x); long yr = fastRound(y); + long seed = m_seed; + double[] cell2dX = CELL_2D_X; + double[] cell2dY = CELL_2D_Y; double distance = 999999; double distance2 = 999999; @@ -1723,46 +1788,61 @@ public class FastNoiseDouble { default: case Euclidean: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { - Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; + double baseVecY = yi - y; + int cellIndex2D = (int) hash2D(seed, xi, yi) & 255; + double vecX = baseVecX + cell2dX[cellIndex2D]; + double vecY = baseVecY + cell2dY[cellIndex2D]; double newDistance = vecX * vecX + vecY * vecY; - distance2 = Math.max(Math.min(distance2, newDistance), distance); - distance = Math.min(distance, newDistance); + if (newDistance < distance) { + distance2 = distance; + distance = newDistance; + } else if (newDistance < distance2) { + distance2 = newDistance; + } } } break; case Manhattan: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { - Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; + double baseVecY = yi - y; + int cellIndex2D = (int) hash2D(seed, xi, yi) & 255; + double vecX = baseVecX + cell2dX[cellIndex2D]; + double vecY = baseVecY + cell2dY[cellIndex2D]; double newDistance = Math.abs(vecX) + Math.abs(vecY); - distance2 = Math.max(Math.min(distance2, newDistance), distance); - distance = Math.min(distance, newDistance); + if (newDistance < distance) { + distance2 = distance; + distance = newDistance; + } else if (newDistance < distance2) { + distance2 = newDistance; + } } } break; case Natural: for (long xi = xr - 1; xi <= xr + 1; xi++) { + double baseVecX = xi - x; for (long yi = yr - 1; yi <= yr + 1; yi++) { - Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255]; - - double vecX = xi - x + vec.x; - double vecY = yi - y + vec.y; + double baseVecY = yi - y; + int cellIndex2D = (int) hash2D(seed, xi, yi) & 255; + double vecX = baseVecX + cell2dX[cellIndex2D]; + double vecY = baseVecY + cell2dY[cellIndex2D]; double newDistance = (Math.abs(vecX) + Math.abs(vecY)) + (vecX * vecX + vecY * vecY); - distance2 = Math.max(Math.min(distance2, newDistance), distance); - distance = Math.min(distance, newDistance); + if (newDistance < distance) { + distance2 = distance; + distance = newDistance; + } else if (newDistance < distance2) { + distance2 = newDistance; + } } } break; @@ -1827,37 +1907,37 @@ public class FastNoiseDouble { } } - Double3 vec0 = CELL_3D[(int) hash3D(seed, x0, y0, z0) & 255]; - Double3 vec1 = CELL_3D[(int) hash3D(seed, x1, y0, z0) & 255]; + int vecIndex000 = (int) hash3D(seed, x0, y0, z0) & 255; + int vecIndex100 = (int) hash3D(seed, x1, y0, z0) & 255; - double lx0x = lerp(vec0.x, vec1.x, xs); - double ly0x = lerp(vec0.y, vec1.y, xs); - double lz0x = lerp(vec0.z, vec1.z, xs); + double lx0x = lerp(CELL_3D_X[vecIndex000], CELL_3D_X[vecIndex100], xs); + double ly0x = lerp(CELL_3D_Y[vecIndex000], CELL_3D_Y[vecIndex100], xs); + double lz0x = lerp(CELL_3D_Z[vecIndex000], CELL_3D_Z[vecIndex100], xs); - vec0 = CELL_3D[(int) hash3D(seed, x0, y1, z0) & 255]; - vec1 = CELL_3D[(int) hash3D(seed, x1, y1, z0) & 255]; + int vecIndex010 = (int) hash3D(seed, x0, y1, z0) & 255; + int vecIndex110 = (int) hash3D(seed, x1, y1, z0) & 255; - double lx1x = lerp(vec0.x, vec1.x, xs); - double ly1x = lerp(vec0.y, vec1.y, xs); - double lz1x = lerp(vec0.z, vec1.z, xs); + double lx1x = lerp(CELL_3D_X[vecIndex010], CELL_3D_X[vecIndex110], xs); + double ly1x = lerp(CELL_3D_Y[vecIndex010], CELL_3D_Y[vecIndex110], xs); + double lz1x = lerp(CELL_3D_Z[vecIndex010], CELL_3D_Z[vecIndex110], xs); double lx0y = lerp(lx0x, lx1x, ys); double ly0y = lerp(ly0x, ly1x, ys); double lz0y = lerp(lz0x, lz1x, ys); - vec0 = CELL_3D[(int) hash3D(seed, x0, y0, z1) & 255]; - vec1 = CELL_3D[(int) hash3D(seed, x1, y0, z1) & 255]; + int vecIndex001 = (int) hash3D(seed, x0, y0, z1) & 255; + int vecIndex101 = (int) hash3D(seed, x1, y0, z1) & 255; - lx0x = lerp(vec0.x, vec1.x, xs); - ly0x = lerp(vec0.y, vec1.y, xs); - lz0x = lerp(vec0.z, vec1.z, xs); + lx0x = lerp(CELL_3D_X[vecIndex001], CELL_3D_X[vecIndex101], xs); + ly0x = lerp(CELL_3D_Y[vecIndex001], CELL_3D_Y[vecIndex101], xs); + lz0x = lerp(CELL_3D_Z[vecIndex001], CELL_3D_Z[vecIndex101], xs); - vec0 = CELL_3D[(int) hash3D(seed, x0, y1, z1) & 255]; - vec1 = CELL_3D[(int) hash3D(seed, x1, y1, z1) & 255]; + int vecIndex011 = (int) hash3D(seed, x0, y1, z1) & 255; + int vecIndex111 = (int) hash3D(seed, x1, y1, z1) & 255; - lx1x = lerp(vec0.x, vec1.x, xs); - ly1x = lerp(vec0.y, vec1.y, xs); - lz1x = lerp(vec0.z, vec1.z, xs); + lx1x = lerp(CELL_3D_X[vecIndex011], CELL_3D_X[vecIndex111], xs); + ly1x = lerp(CELL_3D_Y[vecIndex011], CELL_3D_Y[vecIndex111], xs); + lz1x = lerp(CELL_3D_Z[vecIndex011], CELL_3D_Z[vecIndex111], xs); v3.x += lerp(lx0y, lerp(lx0x, lx1x, ys), zs) * perturbAmp; v3.y += lerp(ly0y, lerp(ly0x, ly1x, ys), zs) * perturbAmp; @@ -1907,17 +1987,17 @@ public class FastNoiseDouble { } } - Double2 vec0 = CELL_2D[(int) hash2D(seed, x0, y0) & 255]; - Double2 vec1 = CELL_2D[(int) hash2D(seed, x1, y0) & 255]; + int vecIndex00 = (int) hash2D(seed, x0, y0) & 255; + int vecIndex10 = (int) hash2D(seed, x1, y0) & 255; - double lx0x = lerp(vec0.x, vec1.x, xs); - double ly0x = lerp(vec0.y, vec1.y, xs); + double lx0x = lerp(CELL_2D_X[vecIndex00], CELL_2D_X[vecIndex10], xs); + double ly0x = lerp(CELL_2D_Y[vecIndex00], CELL_2D_Y[vecIndex10], xs); - vec0 = CELL_2D[(int) hash2D(seed, x0, y1) & 255]; - vec1 = CELL_2D[(int) hash2D(seed, x1, y1) & 255]; + int vecIndex01 = (int) hash2D(seed, x0, y1) & 255; + int vecIndex11 = (int) hash2D(seed, x1, y1) & 255; - double lx1x = lerp(vec0.x, vec1.x, xs); - double ly1x = lerp(vec0.y, vec1.y, xs); + double lx1x = lerp(CELL_2D_X[vecIndex01], CELL_2D_X[vecIndex11], xs); + double ly1x = lerp(CELL_2D_Y[vecIndex01], CELL_2D_Y[vecIndex11], xs); v2.x += lerp(lx0x, lx1x, ys) * perturbAmp; v2.y += lerp(ly0x, ly1x, ys) * perturbAmp; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/FractalFBMSimplexNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/FractalFBMSimplexNoise.java index 61b6e9448..961e2612b 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/FractalFBMSimplexNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/FractalFBMSimplexNoise.java @@ -38,16 +38,31 @@ public class FractalFBMSimplexNoise implements NoiseGenerator, OctaveNoise { return f(n.GetSimplexFractal(x, 0d)); } + @Override + public double noiseSigned(double x) { + return n.GetSimplexFractal(x, 0D); + } + @Override public double noise(double x, double z) { return f(n.GetSimplexFractal(x, z)); } + @Override + public double noiseSigned(double x, double z) { + return n.GetSimplexFractal(x, z); + } + @Override public double noise(double x, double y, double 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 public void setOctaves(int o) { n.setFractalOctaves(o); diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/HexJamesNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/HexJamesNoise.java index 135ab260c..77542bb50 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/HexJamesNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexJamesNoise.java @@ -24,6 +24,7 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise { {-1L, 1L}, {0L, 1L} }; + private static final ThreadLocal SCRATCH = ThreadLocal.withInitial(HexScratch::new); private final long seed; private final SimplexNoise heatSimplex; private int octaves; @@ -221,6 +222,9 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise { } 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 rWorld = TWO_OVER_THREE * z; long[] rounded = roundAxial(qWorld, rWorld); @@ -233,8 +237,6 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise { double heat = cellHeat(centerQ, centerR, nodeHash, 0); 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); if (childIndex < 0) { @@ -285,4 +287,9 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise { public void setOctaves(int 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]; + } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/HexRandomSizeNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/HexRandomSizeNoise.java index d6a518c0a..9b034946f 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/HexRandomSizeNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexRandomSizeNoise.java @@ -24,6 +24,7 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise { {-1L, 1L}, {0L, 1L} }; + private static final ThreadLocal SCRATCH = ThreadLocal.withInitial(HexScratch::new); private final long seed; private final SimplexNoise heatSimplex; private final SimplexNoise structureSimplex; @@ -186,6 +187,9 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise { } 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 rWorld = TWO_OVER_THREE * z; long[] rounded = roundAxial(qWorld, rWorld); @@ -202,8 +206,6 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise { return heat; } - double[] centersQ = new double[7]; - double[] centersR = new double[7]; int childIndex = pickChildIndex(localQ, localR, centersQ, centersR); if (childIndex < 0) { @@ -250,4 +252,9 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise { public void setOctaves(int 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]; + } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/NoiseGenerator.java b/core/src/main/java/art/arcane/iris/util/project/noise/NoiseGenerator.java index b7d10b529..cf01f4e7a 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/NoiseGenerator.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/NoiseGenerator.java @@ -27,6 +27,18 @@ public interface NoiseGenerator { 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() { return false; } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/OffsetNoiseGenerator.java b/core/src/main/java/art/arcane/iris/util/project/noise/OffsetNoiseGenerator.java index 7c219471c..fb3185172 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/OffsetNoiseGenerator.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/OffsetNoiseGenerator.java @@ -6,11 +6,31 @@ import org.jetbrains.annotations.NotNull; 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 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) { 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); ox = 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 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 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 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 @@ -45,4 +116,4 @@ public class OffsetNoiseGenerator implements NoiseGenerator { public NoiseGenerator getBase() { return base; } -} \ No newline at end of file +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/PerlinNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/PerlinNoise.java index 06b0672f7..6cd80cefa 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/PerlinNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/PerlinNoise.java @@ -51,6 +51,33 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise { 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 public double noise(double x, double z) { if (octaves <= 1) { @@ -69,6 +96,26 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise { 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 public double noise(double x, double y, double z) { if (octaves <= 1) { @@ -87,6 +134,26 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise { 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 public void setOctaves(int o) { octaves = o; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/SimplexNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/SimplexNoise.java index 6cb8227c6..5c65a0fa2 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/SimplexNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/SimplexNoise.java @@ -51,6 +51,33 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise { 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 public double noise(double x, double z) { if (octaves <= 1) { @@ -69,6 +96,26 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise { 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 public double noise(double x, double y, double z) { if (octaves <= 1) { @@ -87,6 +134,26 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise { 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 public void setOctaves(int o) { octaves = o; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/VascularNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/VascularNoise.java index cfa7e0487..f58119c51 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/VascularNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/VascularNoise.java @@ -18,7 +18,6 @@ package art.arcane.iris.util.project.noise; -import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.RNG; public class VascularNoise implements NoiseGenerator { @@ -32,7 +31,20 @@ public class VascularNoise implements NoiseGenerator { } 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 @@ -40,13 +52,28 @@ public class VascularNoise implements NoiseGenerator { return filter(n.GetCellular(x, 0)); } + @Override + public double noiseSigned(double x) { + return filterSigned(n.GetCellular(x, 0D)); + } + @Override public double noise(double x, double z) { return filter(n.GetCellular(x, z)); } + @Override + public double noiseSigned(double x, double z) { + return filterSigned(n.GetCellular(x, z)); + } + @Override public double noise(double x, double y, double 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)); + } } diff --git a/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java b/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java index a759686d1..7bface313 100644 --- a/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java +++ b/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java @@ -281,6 +281,11 @@ public interface ProceduralStream extends ProceduralLayer, Interpolated { return new CachedStream2D(name, engine, this, size); } + @SuppressWarnings("unchecked") + default ProceduralStream cache2DDouble(String name, Engine engine, int size) { + return new CachedDoubleStream2D(name, engine, (ProceduralStream) this, size); + } + default ProceduralStream cache3D(String name, Engine engine, int maxSize) { return new CachedStream3D(name, engine, this, maxSize); } diff --git a/core/src/main/java/art/arcane/iris/util/project/stream/utility/CachedDoubleStream2D.java b/core/src/main/java/art/arcane/iris/util/project/stream/utility/CachedDoubleStream2D.java new file mode 100644 index 000000000..574f824cc --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/stream/utility/CachedDoubleStream2D.java @@ -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 implements ProceduralStream, MeteredCache, ChunkFillableStream2D { + private final ProceduralStream stream; + private final WorldCache2DDouble cache; + private final Engine engine; + + public CachedDoubleStream2D(String name, Engine engine, ProceduralStream 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); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/stream/utility/CachedStream2D.java b/core/src/main/java/art/arcane/iris/util/project/stream/utility/CachedStream2D.java index e99275e27..8a5b5a124 100644 --- a/core/src/main/java/art/arcane/iris/util/project/stream/utility/CachedStream2D.java +++ b/core/src/main/java/art/arcane/iris/util/project/stream/utility/CachedStream2D.java @@ -27,7 +27,7 @@ import art.arcane.volmlib.util.cache.WorldCache2D; import art.arcane.volmlib.util.data.KCache; import art.arcane.iris.util.project.stream.BasicStream; import art.arcane.iris.util.project.stream.ProceduralStream; -public class CachedStream2D extends BasicStream implements ProceduralStream, MeteredCache { +public class CachedStream2D extends BasicStream implements ProceduralStream, MeteredCache, ChunkFillableStream2D { private final ProceduralStream stream; private final WorldCache2D cache; private final Engine engine; @@ -81,7 +81,7 @@ public class CachedStream2D extends BasicStream implements ProceduralStrea 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 chunkZ = worldZ >> 4; cache.fillChunk(chunkX, chunkZ, target); diff --git a/core/src/main/java/art/arcane/iris/util/project/stream/utility/ChunkFillableStream2D.java b/core/src/main/java/art/arcane/iris/util/project/stream/utility/ChunkFillableStream2D.java new file mode 100644 index 000000000..e63eea603 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/stream/utility/ChunkFillableStream2D.java @@ -0,0 +1,5 @@ +package art.arcane.iris.util.project.stream.utility; + +public interface ChunkFillableStream2D { + void fillChunkRaw(int worldX, int worldZ, Object[] target); +} diff --git a/core/src/test/java/art/arcane/iris/core/pregenerator/IrisPregeneratorInitTest.java b/core/src/test/java/art/arcane/iris/core/pregenerator/IrisPregeneratorInitTest.java new file mode 100644 index 000000000..3a9bdd9db --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/pregenerator/IrisPregeneratorInitTest.java @@ -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) { + } + } +} diff --git a/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java b/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java index 072a23acd..1113b8403 100644 --- a/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java +++ b/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java @@ -29,4 +29,16 @@ public class AsyncPregenMethodConcurrencyCapTest { assertEquals(16, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, false, 8)); 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)); + } } diff --git a/core/src/test/java/art/arcane/iris/core/service/IrisEngineSVCTest.java b/core/src/test/java/art/arcane/iris/core/service/IrisEngineSVCTest.java new file mode 100644 index 000000000..30ebb7c12 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/service/IrisEngineSVCTest.java @@ -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)); + } +} diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisMathNoiseHotPathParityTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisMathNoiseHotPathParityTest.java new file mode 100644 index 000000000..c311c91e8 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisMathNoiseHotPathParityTest.java @@ -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().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 + ); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CNGFastPathParityTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CNGFastPathParityTest.java index 183c89167..3c80e4a0b 100644 --- a/core/src/test/java/art/arcane/iris/util/project/noise/CNGFastPathParityTest.java +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CNGFastPathParityTest.java @@ -27,6 +27,16 @@ public class CNGFastPathParityTest { } } + @Test + public void signedFastPathMatchesLegacyAcrossRepresentativeGenerators() { + for (long seed = 31L; seed <= 35L; seed++) { + List 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) { for (int x = -320; x <= 320; x += 19) { 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) { DeterministicNoiseGenerator generator = new DeterministicNoiseGenerator(0.31D + (seed * 0.01D)); return new CNG(new RNG(seed), generator, 1D, 1).bake(); @@ -72,6 +102,24 @@ public class CNGFastPathParityTest { return generators; } + private List createSignedGenerators(long seed) { + List 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 final double offset; diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/FastNoiseDoubleCellularParityTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/FastNoiseDoubleCellularParityTest.java new file mode 100644 index 000000000..4dc7ebc7c --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/FastNoiseDoubleCellularParityTest.java @@ -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); + } + } +}