From 50894b90ce97aab611d20881d0ca04f24171c3f2 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sat, 5 Sep 2026 19:32:13 -0400 Subject: [PATCH] Refined existing noise - Hexagon styles produce coherent solid cells with contained subdivisions - Sierpinski triangles use equilateral recursive geometry - Noise styles share a 64-block base scale - Zoom scales the complete pattern with its distortion - Octaves preserve the base seed and feature scale - Coordinate dispatch uses the native dimensional sampler - Classic kernels retain seed precision and bounded output - Interpolation preserves fractional coordinates and symmetric curves - Noise caches respect sampling bounds and generation identity - Clover sampling reuses vertices and cached trigonometry - Noise and terrain tests cover corrected sampling behavior --- ...ucturePostProcessorSurfaceTerrainTest.java | 2 +- .../engine/object/IrisGeneratorStyle.java | 70 ++- .../engine/object/IrisNoiseGenerator.java | 7 +- .../arcane/iris/engine/object/NoiseStyle.java | 424 +++++++++--------- .../interpolation/IrisInterpolation.java | 264 ++++++----- .../arcane/iris/util/project/noise/CNG.java | 188 ++++---- .../util/project/noise/CellHeightNoise.java | 2 +- .../util/project/noise/CellularNoise.java | 16 +- .../iris/util/project/noise/CloverNoise.java | 123 +++-- .../iris/util/project/noise/FastNoise.java | 8 +- .../util/project/noise/FastNoiseDouble.java | 106 ++++- .../noise/FractalBillowPerlinNoise.java | 1 + .../util/project/noise/FractalCubicNoise.java | 8 +- .../noise/FractalRigidMultiSimplexNoise.java | 2 +- .../util/project/noise/HexJamesNoise.java | 311 +++---------- .../project/noise/HexRandomSizeNoise.java | 288 +++--------- .../util/project/noise/HexSimplexNoise.java | 127 +----- .../iris/util/project/noise/HexagonNoise.java | 85 +--- .../util/project/noise/HexagonalNoise.java | 71 +++ .../util/project/noise/InterpolatedNoise.java | 21 +- .../iris/util/project/noise/NoiseType.java | 124 ++--- .../project/noise/OffsetNoiseGenerator.java | 9 +- .../iris/util/project/noise/PerlinNoise.java | 104 ++--- .../noise/SierpinskiTriangleNoise.java | 119 ++--- .../iris/util/project/noise/SimplexNoise.java | 104 ++--- .../util/project/noise/VascularNoise.java | 3 +- .../iris/util/project/noise/WhiteNoise.java | 16 +- .../IrisCaveCarver3DNearParityTest.java | 22 +- .../StructureCarveEnvelopeTest.java | 3 +- .../IrisMathNoiseHotPathParityTest.java | 14 +- .../object/IrisNoiseGeneratorCacheTest.java | 5 +- .../object/IrisNoiseGeneratorRemapTest.java | 19 + .../engine/object/NoiseStyleContractTest.java | 201 +++++++++ .../IrisInterpolationBoundsTest.java | 22 +- .../IrisInterpolationSamplingTest.java | 168 +++++++ .../noise/CNGDimensionDispatchTest.java | 260 +++++++++++ .../project/noise/CNGInjectorParityTest.java | 4 +- .../noise/CellularNoisePrecisionTest.java | 57 +++ .../noise/ClassicNoiseQualityTest.java | 178 ++++++++ .../noise/CloverNoiseBoundaryTest.java | 46 ++ .../project/noise/CloverNoiseParityTest.java | 62 +++ .../FastNoiseDoubleCellularParityTest.java | 61 ++- .../project/noise/FastNoiseFloorTest.java | 33 ++ .../project/noise/GeometricNoiseTest.java | 156 +++++++ .../noise/InterpolatedNoiseDimensionTest.java | 87 ++++ .../util/project/noise/NoiseOctaveTest.java | 104 +++++ .../util/project/noise/WhiteNoiseTest.java | 40 ++ 47 files changed, 2603 insertions(+), 1542 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/HexagonalNoise.java create mode 100644 core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorRemapTest.java create mode 100644 core/src/test/java/art/arcane/iris/engine/object/NoiseStyleContractTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationSamplingTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/CNGDimensionDispatchTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/CellularNoisePrecisionTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/ClassicNoiseQualityTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseBoundaryTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/FastNoiseFloorTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/GeometricNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/InterpolatedNoiseDimensionTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/NoiseOctaveTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/WhiteNoiseTest.java diff --git a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java index cd8704091..1a53c790a 100644 --- a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java +++ b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java @@ -87,7 +87,7 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; public class NativeStructurePostProcessorSurfaceTerrainTest { - private static final int SLAB_DEPTH = 128; + private static final int SLAB_DEPTH = 512; private static final int SLAB_MIN_Y = 64; private static final int SLAB_PADDING = 14; private static final int SLAB_WIDTH = 16; 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 7f878a37e..b37dfc37e 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 @@ -21,6 +21,7 @@ package art.arcane.iris.engine.object; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.engine.data.cache.LazyBoundedCache; import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.engine.history.GenerationKernelRegistry; import art.arcane.iris.engine.object.annotations.Desc; import art.arcane.iris.engine.object.annotations.MaxNumber; import art.arcane.iris.engine.object.annotations.MinNumber; @@ -40,6 +41,7 @@ import lombok.Setter; import lombok.experimental.Accessors; import java.io.File; +import java.io.IOException; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -65,7 +67,7 @@ public class IrisGeneratorStyle { @Desc("Cell zooms") private double cellularZoom = 1; @MinNumber(0.00001) - @Desc("The zoom of this style") + @Desc("Feature size multiplier. Built-in coherent noise uses a 64-block base scale; 2 doubles feature size.") private double zoom = 1; @Desc("Instead of using the style property, use a custom expression to represent this style.") @RegistryListResource(IrisExpression.class) @@ -97,11 +99,15 @@ public class IrisGeneratorStyle { } public CNG createNoCache(RNG rng, IrisData data) { - return createNoCache(rng, data, false, 0, false, resolveEngine(data)); + return createNoCache(rng, data, 1, 0, false, resolveEngine(data)); + } + + public CNG createForLayer(RNG rng, IrisData data, int octaveMultiplier) { + return createNoCache(rng, data, Math.clamp(octaveMultiplier, 1, 16), 0, false, resolveEngine(data)); } public CNG createForPrebake(RNG rng, IrisData data, int fallbackCacheSize) { - return createNoCache(rng, data, false, Math.max(0, fallbackCacheSize), true, resolveEngine(data)); + return createNoCache(rng, data, 1, Math.max(0, fallbackCacheSize), true, resolveEngine(data)); } @@ -121,16 +127,17 @@ public class IrisGeneratorStyle { return hash(); } - private String cachePrefix(RNG rng, int effectiveCacheSize, long engineStamp) { - return "style-" + Integer.toUnsignedString(hash()) + private String cachePrefix(RNG rng, int effectiveCacheSize, long engineStamp, int octaveMultiplier) { + return "noise-" + NoiseCacheIdentity.FINGERPRINT + "-style-" + Integer.toUnsignedString(hash()) + "-seed-" + Long.toUnsignedString(rng.getSeed()) + "-eng-" + Long.toUnsignedString(engineStamp) + + "-oct-" + octaveMultiplier + "-sz-" + effectiveCacheSize + "-src-"; } - private String cacheKey(RNG rng, long sourceStamp, int effectiveCacheSize, long engineStamp) { - return cachePrefix(rng, effectiveCacheSize, engineStamp) + Long.toUnsignedString(sourceStamp); + private String cacheKey(RNG rng, long sourceStamp, int effectiveCacheSize, long engineStamp, int octaveMultiplier) { + return cachePrefix(rng, effectiveCacheSize, engineStamp, octaveMultiplier) + Long.toUnsignedString(sourceStamp); } private void clearStaleCacheEntries(IrisData data, String prefix, String key) { @@ -157,12 +164,19 @@ public class IrisGeneratorStyle { } } - public CNG createNoCache(RNG rng, IrisData data, boolean actuallyCached) { - return createNoCache(rng, data, actuallyCached, 0, false, resolveEngine(data)); - } - - private CNG createNoCache(RNG rng, IrisData data, boolean actuallyCached, int fallbackCacheSize, + private CNG createNoCache(RNG rng, IrisData data, int octaveMultiplier, int fallbackCacheSize, boolean quietCacheLog, Engine engine) { + requirePositiveFinite("zoom", zoom); + requirePositiveFinite("exponent", exponent); + if (!Double.isFinite(cellularFrequency) || cellularFrequency < 0D) { + throw new IllegalArgumentException("cellularFrequency must be finite and nonnegative"); + } + if (cellularFrequency > 0D) { + requirePositiveFinite("cellularZoom", cellularZoom); + } + if (fracture != null) { + requirePositiveFinite("fracture.multiplier", fracture.getMultiplier()); + } CNG cng = null; long sourceStamp = 0L; if (getExpression() != null) { @@ -182,10 +196,11 @@ public class IrisGeneratorStyle { cng = (style != null ? style : NoiseStyle.FLAT).create(rng).bake(); } - cng = cng.scale(1D / zoom).pow(exponent).bake(); + cng = cng.oct(Math.clamp((long) cng.getOct() * octaveMultiplier, 1, 16)) + .pow(cng.getPower() * exponent); if (fracture != null) { - cng.fractureWith(fracture.createNoCache(rng.nextParallelRNG(2934), data, false, + cng.fractureWith(fracture.createNoCache(rng.nextParallelRNG(2934), data, 1, fallbackCacheSize, quietCacheLog, engine), fracture.getMultiplier()); } @@ -193,11 +208,13 @@ public class IrisGeneratorStyle { cng = cng.cellularize(rng.nextParallelRNG(884466), cellularFrequency).scale(1D / cellularZoom).bake(); } + cng.zoom(zoom); + int effectiveCacheSize = cacheSize > 0 ? cacheSize : Math.max(0, fallbackCacheSize); if (effectiveCacheSize > 0) { long engineStamp = engineStamp(engine); - String key = cacheKey(rng, sourceStamp, effectiveCacheSize, engineStamp); - clearStaleCacheEntries(data, cachePrefix(rng, effectiveCacheSize, engineStamp), key); + String key = cacheKey(rng, sourceStamp, effectiveCacheSize, engineStamp, octaveMultiplier); + clearStaleCacheEntries(data, cachePrefix(rng, effectiveCacheSize, engineStamp, octaveMultiplier), key); cng = cng.cached(effectiveCacheSize, key, data.getDataFolder(), quietCacheLog); } @@ -215,7 +232,7 @@ public class IrisGeneratorStyle { public CNG create(RNG rng, IrisData data, Engine engine) { GeneratorCacheKey key = new GeneratorCacheKey(data, engine, rng.getSeed()); return generatorCache.computeIfAbsent(key, - ignored -> createNoCache(rng, data, true, 0, false, engine)); + ignored -> createNoCache(rng, data, 1, 0, false, engine)); } @SuppressWarnings("BooleanMethodIsAlwaysInverted") @@ -244,6 +261,25 @@ public class IrisGeneratorStyle { return data == null ? null : data.getEngine(); } + private static void requirePositiveFinite(String field, double value) { + if (!Double.isFinite(value) || value <= 0D || !Double.isFinite(1D / value)) { + throw new IllegalArgumentException(field + " must be finite and positive"); + } + } + + private static final class NoiseCacheIdentity { + private static final String FINGERPRINT = fingerprint(); + + private static String fingerprint() { + GenerationKernelRegistry registry = GenerationKernelRegistry.standard(); + try { + return registry.requireSupported(registry.current()).implementationFingerprint(); + } catch (IOException failure) { + throw new IllegalStateException("Cannot identify the noise cache implementation", failure); + } + } + } + private static final class GeneratorCacheKey { private final IrisData data; private final Engine engine; 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 fdce91245..2b8cc7350 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 @@ -90,7 +90,8 @@ public class IrisNoiseGenerator { @Desc("The Noise Style") private IrisGeneratorStyle style = NoiseStyle.IRIS.style(); @MinNumber(1) - @Desc("Multiple octaves for multple generators of changing zooms added together") + @MaxNumber(16) + @Desc("Multiplies the style's octave count, capped at 16. Adds finer detail while preserving the base feature scale.") private int octaves = 1; @ArrayType(min = 1, type = IrisNoiseGenerator.class) @Desc("Apply a child noise generator to fracture the input coordinates of this generator") @@ -111,7 +112,7 @@ public class IrisNoiseGenerator { GeneratorKey key = new GeneratorKey(data, engine, generatorSeed); CNG generator = generators.computeIfAbsent(key, - ignored -> style.createNoCache(new RNG(generatorSeed), data).oct(octaves)); + ignored -> style.createForLayer(new RNG(generatorSeed), data, octaves)); if (generator != null) { recentGenerator.set(new CachedGenerator(key, generator)); } @@ -146,7 +147,7 @@ public class IrisNoiseGenerator { 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; + n = parametric ? IrisInterpolation.parametric(n, 2D) : n; n = bezier ? IrisInterpolation.bezier(n) : n; n = sinCentered ? IrisInterpolation.sinCenter(n) : n; diff --git a/core/src/main/java/art/arcane/iris/engine/object/NoiseStyle.java b/core/src/main/java/art/arcane/iris/engine/object/NoiseStyle.java index 1a08e9afe..fd339091c 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/NoiseStyle.java +++ b/core/src/main/java/art/arcane/iris/engine/object/NoiseStyle.java @@ -27,523 +27,529 @@ import art.arcane.iris.util.project.stream.ProceduralStream; @Desc("Styles of noise") public enum NoiseStyle { @Desc("White Noise is like static. Useful for block scattering but not terrain.") - STATIC(rng -> new CNG(rng, NoiseType.WHITE, 1D, 1)), + STATIC(rng -> new CNG(rng, NoiseType.WHITE, 1D, 1), NoiseType.WHITE), @Desc("White Noise is like static. Useful for block scattering but not terrain.") - STATIC_BILINEAR(rng -> new CNG(rng, NoiseType.WHITE_BILINEAR, 1D, 1)), + STATIC_BILINEAR(rng -> new CNG(rng, NoiseType.WHITE_BILINEAR, 1D, 1), NoiseType.WHITE_BILINEAR), @Desc("White Noise is like static. Useful for block scattering but not terrain.") - STATIC_BICUBIC(rng -> new CNG(rng, NoiseType.WHITE_BICUBIC, 1D, 1)), + STATIC_BICUBIC(rng -> new CNG(rng, NoiseType.WHITE_BICUBIC, 1D, 1), NoiseType.WHITE_BICUBIC), @Desc("White Noise is like static. Useful for block scattering but not terrain.") - STATIC_HERMITE(rng -> new CNG(rng, NoiseType.WHITE_HERMITE, 1D, 1)), + STATIC_HERMITE(rng -> new CNG(rng, NoiseType.WHITE_HERMITE, 1D, 1), NoiseType.WHITE_HERMITE), @Desc("Wispy Perlin-looking simplex noise. The 'iris' style noise.") - IRIS(rng -> CNG.signature(rng).scale(1)), + IRIS(rng -> CNG.signature(rng)), @Desc("Clover Noise") - CLOVER(rng -> new CNG(rng, NoiseType.CLOVER, 1D, 1).scale(0.06).bake()), + CLOVER(rng -> new CNG(rng, NoiseType.CLOVER, 1D, 1).bake(), NoiseType.CLOVER), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_STARCAST_3(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_3, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_STARCAST_3(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_3, 1D, 1), NoiseType.CLOVER_STARCAST_3), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_STARCAST_6(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_6, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_STARCAST_6(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_6, 1D, 1), NoiseType.CLOVER_STARCAST_6), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_STARCAST_9(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_9, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_STARCAST_9(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_9, 1D, 1), NoiseType.CLOVER_STARCAST_9), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_STARCAST_12(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_12, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_STARCAST_12(rng -> new CNG(rng, NoiseType.CLOVER_STARCAST_12, 1D, 1), NoiseType.CLOVER_STARCAST_12), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_BILINEAR_STARCAST_3(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_3, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_BILINEAR_STARCAST_3(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_3, 1D, 1), NoiseType.CLOVER_BILINEAR_STARCAST_3), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_BILINEAR_STARCAST_6(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_6, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_BILINEAR_STARCAST_6(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_6, 1D, 1), NoiseType.CLOVER_BILINEAR_STARCAST_6), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_BILINEAR_STARCAST_9(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_9, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_BILINEAR_STARCAST_9(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_9, 1D, 1), NoiseType.CLOVER_BILINEAR_STARCAST_9), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_BILINEAR_STARCAST_12(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_12, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_BILINEAR_STARCAST_12(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR_STARCAST_12, 1D, 1), NoiseType.CLOVER_BILINEAR_STARCAST_12), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_HERMITE_STARCAST_3(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_3, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_HERMITE_STARCAST_3(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_3, 1D, 1), NoiseType.CLOVER_HERMITE_STARCAST_3), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_HERMITE_STARCAST_6(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_6, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_HERMITE_STARCAST_6(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_6, 1D, 1), NoiseType.CLOVER_HERMITE_STARCAST_6), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_HERMITE_STARCAST_9(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_9, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_HERMITE_STARCAST_9(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_9, 1D, 1), NoiseType.CLOVER_HERMITE_STARCAST_9), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_HERMITE_STARCAST_12(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_12, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_HERMITE_STARCAST_12(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE_STARCAST_12, 1D, 1), NoiseType.CLOVER_HERMITE_STARCAST_12), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_BILINEAR(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_BILINEAR(rng -> new CNG(rng, NoiseType.CLOVER_BILINEAR, 1D, 1), NoiseType.CLOVER_BILINEAR), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_BICUBIC(rng -> new CNG(rng, NoiseType.CLOVER_BICUBIC, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_BICUBIC(rng -> new CNG(rng, NoiseType.CLOVER_BICUBIC, 1D, 1), NoiseType.CLOVER_BICUBIC), - @Desc("CLOVER noise creates the same noise level for cells, changes noise level on cell borders.") - CLOVER_HERMITE(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE, 1D, 1)), + @Desc("Clover noise smoothed with the selected interpolation kernel.") + CLOVER_HERMITE(rng -> new CNG(rng, NoiseType.CLOVER_HERMITE, 1D, 1), NoiseType.CLOVER_HERMITE), @Desc("Vascular noise gets higher as the position nears a cell border.") - VASCULAR(rng -> new CNG(rng, NoiseType.VASCULAR, 1D, 1)), + VASCULAR(rng -> new CNG(rng, NoiseType.VASCULAR, 1D, 1), NoiseType.VASCULAR), - @Desc("It always returns 0.5") - FLAT(rng -> new CNG(rng, NoiseType.FLAT, 1D, 1)), + @Desc("It always returns 1.0") + FLAT(rng -> new CNG(rng, NoiseType.FLAT, 1D, 1), NoiseType.FLAT), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR(rng -> new CNG(rng, NoiseType.CELLULAR, 1D, 1)), + CELLULAR(rng -> new CNG(rng, NoiseType.CELLULAR, 1D, 1), NoiseType.CELLULAR), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_STARCAST_3(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_3, 1D, 1)), + CELLULAR_STARCAST_3(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_3, 1D, 1), NoiseType.CELLULAR_STARCAST_3), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_STARCAST_6(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_6, 1D, 1)), + CELLULAR_STARCAST_6(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_6, 1D, 1), NoiseType.CELLULAR_STARCAST_6), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_STARCAST_9(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_9, 1D, 1)), + CELLULAR_STARCAST_9(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_9, 1D, 1), NoiseType.CELLULAR_STARCAST_9), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_STARCAST_12(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_12, 1D, 1)), + CELLULAR_STARCAST_12(rng -> new CNG(rng, NoiseType.CELLULAR_STARCAST_12, 1D, 1), NoiseType.CELLULAR_STARCAST_12), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_BILINEAR_STARCAST_3(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_3, 1D, 1)), + CELLULAR_BILINEAR_STARCAST_3(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_3, 1D, 1), NoiseType.CELLULAR_BILINEAR_STARCAST_3), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_BILINEAR_STARCAST_6(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_6, 1D, 1)), + CELLULAR_BILINEAR_STARCAST_6(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_6, 1D, 1), NoiseType.CELLULAR_BILINEAR_STARCAST_6), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_BILINEAR_STARCAST_9(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_9, 1D, 1)), + CELLULAR_BILINEAR_STARCAST_9(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_9, 1D, 1), NoiseType.CELLULAR_BILINEAR_STARCAST_9), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_BILINEAR_STARCAST_12(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_12, 1D, 1)), + CELLULAR_BILINEAR_STARCAST_12(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR_STARCAST_12, 1D, 1), NoiseType.CELLULAR_BILINEAR_STARCAST_12), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_HERMITE_STARCAST_3(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_3, 1D, 1)), + CELLULAR_HERMITE_STARCAST_3(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_3, 1D, 1), NoiseType.CELLULAR_HERMITE_STARCAST_3), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_HERMITE_STARCAST_6(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_6, 1D, 1)), + CELLULAR_HERMITE_STARCAST_6(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_6, 1D, 1), NoiseType.CELLULAR_HERMITE_STARCAST_6), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_HERMITE_STARCAST_9(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_9, 1D, 1)), + CELLULAR_HERMITE_STARCAST_9(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_9, 1D, 1), NoiseType.CELLULAR_HERMITE_STARCAST_9), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_HERMITE_STARCAST_12(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_12, 1D, 1)), + CELLULAR_HERMITE_STARCAST_12(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE_STARCAST_12, 1D, 1), NoiseType.CELLULAR_HERMITE_STARCAST_12), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_BILINEAR(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR, 1D, 1)), + CELLULAR_BILINEAR(rng -> new CNG(rng, NoiseType.CELLULAR_BILINEAR, 1D, 1), NoiseType.CELLULAR_BILINEAR), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_BICUBIC(rng -> new CNG(rng, NoiseType.CELLULAR_BICUBIC, 1D, 1)), + CELLULAR_BICUBIC(rng -> new CNG(rng, NoiseType.CELLULAR_BICUBIC, 1D, 1), NoiseType.CELLULAR_BICUBIC), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders.") - CELLULAR_HERMITE(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE, 1D, 1)), + CELLULAR_HERMITE(rng -> new CNG(rng, NoiseType.CELLULAR_HERMITE, 1D, 1), NoiseType.CELLULAR_HERMITE), - @Desc("Hexagonal cells with stable per-cell values.") - HEXAGON(rng -> new CNG(rng, NoiseType.HEXAGON, 1D, 1).scale(1)), + @Desc("Solid regular hexagons colored by a coherent simplex field.") + HEXAGON(rng -> new CNG(rng, NoiseType.HEXAGON, 1D, 1), NoiseType.HEXAGON), - @Desc("Hex James substitution pattern with 3 large and 3 small recursive hex children plus gaps.") - HEX_JAMES(rng -> new CNG(rng, NoiseType.HEX_JAMES, 1D, 1).scale(1)), + @Desc("Recursive contained hexagons with alternating subdivision probabilities and simplex colors.") + HEX_JAMES(rng -> new CNG(rng, NoiseType.HEX_JAMES, 1D, 1), NoiseType.HEX_JAMES), @Desc("Interlocked solid hex cells with per-cell values from a smooth simplex heatmap.") - HEX_SIMPLEX(rng -> new CNG(rng, NoiseType.HEX_SIMPLEX, 1D, 1).scale(1)), + HEX_SIMPLEX(rng -> new CNG(rng, NoiseType.HEX_SIMPLEX, 1D, 1), NoiseType.HEX_SIMPLEX), - @Desc("Randomized finite-depth Sierpinski-style hex recursion with per-level random 3-large/3-small James substitution.") - HEX_RANDOM_SIZE(rng -> new CNG(rng, NoiseType.HEX_RANDOM_SIZE, 1D, 1).scale(1)), + @Desc("Finite-depth hexagonal subdivision selected by a seeded field, with coherent simplex colors.") + HEX_RANDOM_SIZE(rng -> new CNG(rng, NoiseType.HEX_RANDOM_SIZE, 1D, 1), NoiseType.HEX_RANDOM_SIZE), - @Desc("Sierpinski triangle mask blended with simplex heat.") - SIERPINSKI_TRIANGLE(rng -> new CNG(rng, NoiseType.SIERPINSKI_TRIANGLE, 1D, 1).scale(1)), + @Desc("Finite-depth equilateral Sierpinski triangles colored by simplex heat.") + SIERPINSKI_TRIANGLE(rng -> new CNG(rng, NoiseType.SIERPINSKI_TRIANGLE, 1D, 1), NoiseType.SIERPINSKI_TRIANGLE), - @Desc("Classic German Engineering") - NOWHERE(rng -> CNG.signaturePerlin(rng).scale(0.776).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE(rng -> CNG.signaturePerlin(rng).bake()), - @Desc("Classic German Engineering") - NOWHERE_CELLULAR(rng -> CNG.signaturePerlin(rng, NoiseType.CELLULAR).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_CELLULAR(rng -> CNG.signaturePerlin(rng, NoiseType.CELLULAR).bake(), NoiseType.CELLULAR), - @Desc("Classic German Engineering") - NOWHERE_CLOVER(rng -> CNG.signaturePerlin(rng, NoiseType.CLOVER).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_CLOVER(rng -> CNG.signaturePerlin(rng, NoiseType.CLOVER).bake(), NoiseType.CLOVER), - @Desc("Classic German Engineering") - NOWHERE_HEXAGON(rng -> CNG.signaturePerlin(rng, NoiseType.HEXAGON).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_HEXAGON(rng -> CNG.signaturePerlin(rng, NoiseType.HEXAGON).bake(), NoiseType.HEXAGON), - @Desc("Classic German Engineering") - NOWHERE_HEX_JAMES(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_JAMES).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_HEX_JAMES(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_JAMES).bake(), NoiseType.HEX_JAMES), - @Desc("Classic German Engineering") - NOWHERE_HEX_SIMPLEX(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_SIMPLEX).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_HEX_SIMPLEX(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_SIMPLEX).bake(), NoiseType.HEX_SIMPLEX), - @Desc("Classic German Engineering") - NOWHERE_HEX_RANDOM_SIZE(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_RANDOM_SIZE).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_HEX_RANDOM_SIZE(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_RANDOM_SIZE).bake(), NoiseType.HEX_RANDOM_SIZE), - @Desc("Classic German Engineering") - NOWHERE_SIERPINSKI_TRIANGLE(rng -> CNG.signaturePerlin(rng, NoiseType.SIERPINSKI_TRIANGLE).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_SIERPINSKI_TRIANGLE(rng -> CNG.signaturePerlin(rng, NoiseType.SIERPINSKI_TRIANGLE).bake(), NoiseType.SIERPINSKI_TRIANGLE), - @Desc("Classic German Engineering") - NOWHERE_SIMPLEX(rng -> CNG.signaturePerlin(rng, NoiseType.SIMPLEX).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_SIMPLEX(rng -> CNG.signaturePerlin(rng, NoiseType.SIMPLEX).bake(), NoiseType.SIMPLEX), - @Desc("Classic German Engineering") - NOWHERE_GLOB(rng -> CNG.signaturePerlin(rng, NoiseType.GLOB).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_GLOB(rng -> CNG.signaturePerlin(rng, NoiseType.GLOB).bake(), NoiseType.GLOB), - @Desc("Classic German Engineering") - NOWHERE_VASCULAR(rng -> CNG.signaturePerlin(rng, NoiseType.VASCULAR).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_VASCULAR(rng -> CNG.signaturePerlin(rng, NoiseType.VASCULAR).bake(), NoiseType.VASCULAR), - @Desc("Classic German Engineering") - NOWHERE_CUBIC(rng -> CNG.signaturePerlin(rng, NoiseType.CUBIC).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_CUBIC(rng -> CNG.signaturePerlin(rng, NoiseType.CUBIC).bake(), NoiseType.CUBIC), - @Desc("Classic German Engineering") - NOWHERE_SUPERFRACTAL(rng -> CNG.signaturePerlin(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_SUPERFRACTAL(rng -> CNG.signaturePerlin(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX).bake(), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), - @Desc("Classic German Engineering") - NOWHERE_FRACTAL(rng -> CNG.signaturePerlin(rng, NoiseType.FRACTAL_BILLOW_PERLIN).scale(1).bake()), + @Desc("Perlin-warped noise with broad, flowing distortion.") + NOWHERE_FRACTAL(rng -> CNG.signaturePerlin(rng, NoiseType.FRACTAL_BILLOW_PERLIN).bake(), NoiseType.FRACTAL_BILLOW_PERLIN), @Desc("Wispy Perlin-looking simplex noise. The 'iris' style noise.") - IRIS_DOUBLE(rng -> CNG.signatureDouble(rng).scale(1)), + IRIS_DOUBLE(rng -> CNG.signatureDouble(rng)), @Desc("Wispy Perlin-looking simplex noise. The 'iris' style noise.") - IRIS_THICK(rng -> CNG.signatureThick(rng).scale(1)), + IRIS_THICK(rng -> CNG.signatureThick(rng)), @Desc("Wispy Perlin-looking simplex noise. The 'iris' style noise.") - IRIS_HALF(rng -> CNG.signatureHalf(rng).scale(1)), + IRIS_HALF(rng -> CNG.signatureHalf(rng)), @Desc("Basic, Smooth & Fast Simplex noise.") - SIMPLEX(rng -> new CNG(rng, 1D, 1).scale(1)), + SIMPLEX(rng -> new CNG(rng, 1D, 1)), @Desc("Very Detailed smoke using simplex fractured with fractal billow simplex at high octaves.") - FRACTAL_SMOKE(rng -> new CNG(rng, 1D, 1).fractureWith(new CNG(rng.nextParallelRNG(1), NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 8).scale(0.2), 1000).scale(0.34)), + FRACTAL_SMOKE(rng -> new CNG(rng, 1D, 1).fractureWith(new CNG(rng.nextParallelRNG(1), NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 8).scale(0.2), 1000), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("Thinner Veins.") - VASCULAR_THIN(rng -> new CNG(rng.nextParallelRNG(1), NoiseType.VASCULAR, 1D, 1).scale(1).pow(0.65)), + VASCULAR_THIN(rng -> new CNG(rng.nextParallelRNG(1), NoiseType.VASCULAR, 1D, 1).scale(1).pow(1D / 0.65D), NoiseType.VASCULAR), @Desc("Cells of simplex noise") - SIMPLEX_CELLS(rng -> new CNG(rng.nextParallelRNG(1), NoiseType.SIMPLEX, 1D, 1).scale(1).fractureWith(new CNG(rng.nextParallelRNG(8), NoiseType.CELLULAR, 1D, 1).scale(1), 200)), + SIMPLEX_CELLS(rng -> new CNG(rng.nextParallelRNG(1), NoiseType.SIMPLEX, 1D, 1).scale(1).fractureWith(new CNG(rng.nextParallelRNG(8), NoiseType.CELLULAR, 1D, 1).scale(1), 200), NoiseType.SIMPLEX), @Desc("Veins of simplex noise") - SIMPLEX_VASCULAR(rng -> new CNG(rng.nextParallelRNG(1), NoiseType.SIMPLEX, 1D, 1).scale(1).fractureWith(new CNG(rng.nextParallelRNG(8), NoiseType.VASCULAR, 1D, 1).scale(1), 200)), + SIMPLEX_VASCULAR(rng -> new CNG(rng.nextParallelRNG(1), NoiseType.SIMPLEX, 1D, 1).scale(1).fractureWith(new CNG(rng.nextParallelRNG(8), NoiseType.VASCULAR, 1D, 1).scale(1), 200), NoiseType.SIMPLEX), @Desc("Very Detailed fluid using simplex fractured with fractal billow simplex at high octaves.") - FRACTAL_WATER(rng -> new CNG(rng, 1D, 1).fractureWith(new CNG(rng.nextParallelRNG(1), NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 9).scale(0.03), 9900).scale(1.14)), + FRACTAL_WATER(rng -> new CNG(rng, 1D, 1).fractureWith(new CNG(rng.nextParallelRNG(1), NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 9).scale(0.03), 9900), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Perlin. Like simplex but more natural") - PERLIN(rng -> new CNG(rng, NoiseType.PERLIN, 1D, 1).scale(1.15)), + PERLIN(rng -> new CNG(rng, NoiseType.PERLIN, 1D, 1), NoiseType.PERLIN), @Desc("Perlin. Like simplex but more natural") - PERLIN_IRIS(rng -> CNG.signature(rng, NoiseType.PERLIN).scale(1.47)), + PERLIN_IRIS(rng -> CNG.signature(rng, NoiseType.PERLIN), NoiseType.PERLIN), @Desc("Perlin. Like simplex but more natural") - PERLIN_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.PERLIN).scale(1.47)), + PERLIN_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.PERLIN), NoiseType.PERLIN), @Desc("Perlin. Like simplex but more natural") - PERLIN_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.PERLIN).scale(1.47)), + PERLIN_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.PERLIN), NoiseType.PERLIN), @Desc("Perlin. Like simplex but more natural") - PERLIN_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.PERLIN).scale(1.47)), + PERLIN_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.PERLIN), NoiseType.PERLIN), @Desc("Billow Fractal Perlin Noise.") - FRACTAL_BILLOW_PERLIN(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_PERLIN, 1D, 1).scale(1.47)), + FRACTAL_BILLOW_PERLIN(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_PERLIN, 1D, 1), NoiseType.FRACTAL_BILLOW_PERLIN), @Desc("Billow Fractal Perlin Noise. 2 Octaves") - BIOCTAVE_FRACTAL_BILLOW_PERLIN(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_PERLIN, 1D, 2).scale(1.17)), + BIOCTAVE_FRACTAL_BILLOW_PERLIN(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_PERLIN, 1D, 2), NoiseType.FRACTAL_BILLOW_PERLIN), @Desc("Billow Fractal Simplex Noise. Single octave.") - FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 1)), + FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 1), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. Single octave.") - FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 1)), + FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 1), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Billow Fractal Iris Noise. Single octave.") - FRACTAL_BILLOW_IRIS(rng -> CNG.signature(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX)), + FRACTAL_BILLOW_IRIS(rng -> CNG.signature(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Iris Noise. Single octave.") - FRACTAL_FBM_IRIS(rng -> CNG.signature(rng, NoiseType.FRACTAL_FBM_SIMPLEX)), + FRACTAL_FBM_IRIS(rng -> CNG.signature(rng, NoiseType.FRACTAL_FBM_SIMPLEX), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Billow Fractal Iris Noise. Single octave.") - FRACTAL_BILLOW_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX)), + FRACTAL_BILLOW_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Iris Noise. Single octave.") - FRACTAL_FBM_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.FRACTAL_FBM_SIMPLEX)), + FRACTAL_FBM_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.FRACTAL_FBM_SIMPLEX), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Billow Fractal Iris Noise. Single octave.") - FRACTAL_BILLOW_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX)), + FRACTAL_BILLOW_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Iris Noise. Single octave.") - FRACTAL_FBM_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.FRACTAL_FBM_SIMPLEX)), + FRACTAL_FBM_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.FRACTAL_FBM_SIMPLEX), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Fractal hexagonal cell noise.") - FRACTAL_HEXAGON(rng -> new CNG(rng, NoiseType.HEXAGON, 1D, 4).scale(1)), + FRACTAL_HEXAGON(rng -> new CNG(rng, NoiseType.HEXAGON, 1D, 4), NoiseType.HEXAGON), - @Desc("Fractal Hex James substitution pattern with recursive 3-large/3-small child hexes and gaps.") - FRACTAL_HEX_JAMES(rng -> new CNG(rng, NoiseType.HEX_JAMES, 1D, 4).scale(1)), + @Desc("Recursive contained hexagons colored by fractal simplex noise.") + FRACTAL_HEX_JAMES(rng -> new CNG(rng, NoiseType.HEX_JAMES, 1D, 4), NoiseType.HEX_JAMES), @Desc("Interlocked solid hex cells with per-cell fractal simplex heatmap values.") - FRACTAL_HEX_SIMPLEX(rng -> new CNG(rng, NoiseType.HEX_SIMPLEX, 1D, 4).scale(1)), + FRACTAL_HEX_SIMPLEX(rng -> new CNG(rng, NoiseType.HEX_SIMPLEX, 1D, 4), NoiseType.HEX_SIMPLEX), - @Desc("Fractal hexagonal gradient noise with randomized local hex sizes and blended random/simplex heatmaps.") - FRACTAL_HEX_RANDOM_SIZE(rng -> new CNG(rng, NoiseType.HEX_RANDOM_SIZE, 1D, 4).scale(1)), + @Desc("Recursive hexagonal subdivision colored by fractal simplex noise.") + FRACTAL_HEX_RANDOM_SIZE(rng -> new CNG(rng, NoiseType.HEX_RANDOM_SIZE, 1D, 4), NoiseType.HEX_RANDOM_SIZE), - @Desc("Fractal Sierpinski triangle mask blended with simplex heat.") - FRACTAL_SIERPINSKI_TRIANGLE(rng -> new CNG(rng, NoiseType.SIERPINSKI_TRIANGLE, 1D, 4).scale(1)), + @Desc("Finite-depth equilateral Sierpinski triangles colored by fractal simplex heat.") + FRACTAL_SIERPINSKI_TRIANGLE(rng -> new CNG(rng, NoiseType.SIERPINSKI_TRIANGLE, 1D, 4), NoiseType.SIERPINSKI_TRIANGLE), @Desc("Rigid Multi Fractal Simplex Noise. Single octave.") - FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 1)), + FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 1), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 2 octaves.") - BIOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 2)), + BIOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 2), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 2 octaves.") - BIOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 2)), + BIOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 2), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 2 octaves.") - BIOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 2)), + BIOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 2), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 3 octaves.") - TRIOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 3)), + TRIOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 3), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 3 octaves.") - TRIOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 3)), + TRIOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 3), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 3 octaves.") - TRIOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 3)), + TRIOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 3), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 4 octaves.") - QUADOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 4)), + QUADOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 4), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 4 octaves.") - QUADOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 4)), + QUADOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 4), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 4 octaves.") - QUADOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 4)), + QUADOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 4), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 5 octaves.") - QUINTOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 5)), + QUINTOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 5), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 5 octaves.") - QUINTOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 5)), + QUINTOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 5), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 5 octaves.") - QUINTOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 5)), + QUINTOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 5), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 6 octaves.") - SEXOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 6)), + SEXOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 6), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 6 octaves.") - SEXOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 6)), + SEXOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 6), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 6 octaves.") - SEXOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 6)), + SEXOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 6), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 7 octaves.") - SEPTOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 7)), + SEPTOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 7), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 7 octaves.") - SEPTOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 7)), + SEPTOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 7), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 7 octaves.") - SEPTOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 7)), + SEPTOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 7), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 8 octaves.") - OCTOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 8)), + OCTOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 8), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 8 octaves.") - OCTOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 8)), + OCTOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 8), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 8 octaves.") - OCTOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 8)), + OCTOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 8), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 9 octaves.") - NONOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 9)), + NONOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 9), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 9 octaves.") - NONOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 9)), + NONOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 9), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 9 octaves.") - NONOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 9)), + NONOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 9), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Rigid Multi Fractal Simplex Noise. 10 octaves.") - VIGOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 10)), + VIGOCTAVE_FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 10), NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX), @Desc("Billow Fractal Simplex Noise. 10 octaves.") - VIGOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 10)), + VIGOCTAVE_FRACTAL_BILLOW_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_BILLOW_SIMPLEX, 1D, 10), NoiseType.FRACTAL_BILLOW_SIMPLEX), @Desc("FBM Fractal Simplex Noise. 10 octaves.") - VIGOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 10)), + VIGOCTAVE_FRACTAL_FBM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_FBM_SIMPLEX, 1D, 10), NoiseType.FRACTAL_FBM_SIMPLEX), @Desc("Basic, Smooth & Fast Simplex noise. Uses 2 octaves") - BIOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 2).scale(1D / 2D)), + BIOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 2)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 3 octaves") - TRIOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 3).scale(1D / 3D)), + TRIOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 3)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 4 octaves") - QUADOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 4).scale(1D / 4D)), + QUADOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 4)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 5 octaves") - QUINTOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 5).scale(1D / 5D)), + QUINTOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 5)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 6 octaves") - SEXOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 6).scale(1D / 6D)), + SEXOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 6)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 7 octaves") - SEPTOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 7).scale(1D / 12D)), + SEPTOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 7)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 8 octaves") - OCTOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 8).scale(1D / 25D)), + OCTOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 8)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 9 octaves") - NONOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 9).scale(1D / 50D)), + NONOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 9)), @Desc("Basic, Smooth & Fast Simplex noise. Uses 10 octaves") - VIGOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 10).scale(1D / 100D)), + VIGOCTAVE_SIMPLEX(rng -> new CNG(rng, 1D, 10)), @Desc("Glob noise is like cellular, but with globs...") - GLOB(rng -> new CNG(rng, NoiseType.GLOB, 1D, 1)), + GLOB(rng -> new CNG(rng, NoiseType.GLOB, 1D, 1), NoiseType.GLOB), @Desc("Glob noise is like cellular, but with globs...") - GLOB_IRIS(rng -> CNG.signature(rng, NoiseType.GLOB)), + GLOB_IRIS(rng -> CNG.signature(rng, NoiseType.GLOB), NoiseType.GLOB), @Desc("Glob noise is like cellular, but with globs...") - GLOB_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.GLOB)), + GLOB_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.GLOB), NoiseType.GLOB), @Desc("Glob noise is like cellular, but with globs...") - GLOB_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.GLOB)), + GLOB_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.GLOB), NoiseType.GLOB), @Desc("Glob noise is like cellular, but with globs...") - GLOB_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.GLOB)), + GLOB_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.GLOB), NoiseType.GLOB), @Desc("Cubic Noise") - CUBIC(rng -> new CNG(rng, NoiseType.CUBIC, 1D, 1).scale(256)), + CUBIC(rng -> new CNG(rng, NoiseType.CUBIC, 1D, 1), NoiseType.CUBIC), @Desc("Fractal Cubic Noise") - FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 1).scale(2)), + FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 1), NoiseType.FRACTAL_CUBIC), @Desc("Fractal Cubic Noise With Iris Swirls") - FRACTAL_CUBIC_IRIS(rng -> CNG.signature(rng, NoiseType.FRACTAL_CUBIC).scale(2)), + FRACTAL_CUBIC_IRIS(rng -> CNG.signature(rng, NoiseType.FRACTAL_CUBIC), NoiseType.FRACTAL_CUBIC), @Desc("Fractal Cubic Noise With Iris Swirls") - FRACTAL_CUBIC_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.FRACTAL_CUBIC).scale(2)), + FRACTAL_CUBIC_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.FRACTAL_CUBIC), NoiseType.FRACTAL_CUBIC), @Desc("Fractal Cubic Noise With Iris Swirls") - FRACTAL_CUBIC_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.FRACTAL_CUBIC).scale(2)), + FRACTAL_CUBIC_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.FRACTAL_CUBIC), NoiseType.FRACTAL_CUBIC), @Desc("Fractal Cubic Noise With Iris Swirls") - FRACTAL_CUBIC_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.FRACTAL_CUBIC).scale(2)), + FRACTAL_CUBIC_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.FRACTAL_CUBIC), NoiseType.FRACTAL_CUBIC), @Desc("Fractal Cubic Noise, 2 Octaves") - BIOCTAVE_FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 2).scale(2)), + BIOCTAVE_FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 2), NoiseType.FRACTAL_CUBIC), @Desc("Fractal Cubic Noise, 3 Octaves") - TRIOCTAVE_FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 3).scale(1.5)), + TRIOCTAVE_FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 3), NoiseType.FRACTAL_CUBIC), @Desc("Fractal Cubic Noise, 4 Octaves") - QUADOCTAVE_FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 4).scale(1)), + QUADOCTAVE_FRACTAL_CUBIC(rng -> new CNG(rng, NoiseType.FRACTAL_CUBIC, 1D, 4), NoiseType.FRACTAL_CUBIC), @Desc("Cubic Noise") - CUBIC_IRIS(rng -> CNG.signature(rng, NoiseType.CUBIC).scale(256)), + CUBIC_IRIS(rng -> CNG.signature(rng, NoiseType.CUBIC), NoiseType.CUBIC), @Desc("Cubic Noise") - CUBIC_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.CUBIC).scale(256)), + CUBIC_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.CUBIC), NoiseType.CUBIC), @Desc("Cubic Noise") - CUBIC_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.CUBIC).scale(256)), + CUBIC_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.CUBIC), NoiseType.CUBIC), @Desc("Cubic Noise") - CUBIC_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.CUBIC).scale(256)), + CUBIC_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.CUBIC), NoiseType.CUBIC), @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") - HEXAGON_IRIS(rng -> CNG.signature(rng, NoiseType.HEXAGON).scale(1)), + HEXAGON_IRIS(rng -> CNG.signature(rng, NoiseType.HEXAGON), NoiseType.HEXAGON), @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") - HEXAGON_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEXAGON).scale(1)), + HEXAGON_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEXAGON), NoiseType.HEXAGON), @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") - HEXAGON_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEXAGON).scale(1)), + HEXAGON_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEXAGON), NoiseType.HEXAGON), @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") - HEXAGON_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEXAGON).scale(1)), + HEXAGON_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEXAGON), NoiseType.HEXAGON), @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") - HEX_JAMES_IRIS(rng -> CNG.signature(rng, NoiseType.HEX_JAMES).scale(1)), + HEX_JAMES_IRIS(rng -> CNG.signature(rng, NoiseType.HEX_JAMES), NoiseType.HEX_JAMES), @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") - HEX_JAMES_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEX_JAMES).scale(1)), + HEX_JAMES_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEX_JAMES), NoiseType.HEX_JAMES), @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") - HEX_JAMES_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEX_JAMES).scale(1)), + HEX_JAMES_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEX_JAMES), NoiseType.HEX_JAMES), @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") - HEX_JAMES_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEX_JAMES).scale(1)), + HEX_JAMES_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEX_JAMES), NoiseType.HEX_JAMES), @Desc("Interlocked solid hex-cell simplex heatmap distorted using Iris styled wispy noise.") - HEX_SIMPLEX_IRIS(rng -> CNG.signature(rng, NoiseType.HEX_SIMPLEX).scale(1)), + HEX_SIMPLEX_IRIS(rng -> CNG.signature(rng, NoiseType.HEX_SIMPLEX), NoiseType.HEX_SIMPLEX), @Desc("Interlocked solid hex-cell simplex heatmap distorted using Iris styled wispy noise.") - HEX_SIMPLEX_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEX_SIMPLEX).scale(1)), + HEX_SIMPLEX_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEX_SIMPLEX), NoiseType.HEX_SIMPLEX), @Desc("Interlocked solid hex-cell simplex heatmap distorted using Iris styled wispy noise.") - HEX_SIMPLEX_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEX_SIMPLEX).scale(1)), + HEX_SIMPLEX_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEX_SIMPLEX), NoiseType.HEX_SIMPLEX), @Desc("Interlocked solid hex-cell simplex heatmap distorted using Iris styled wispy noise.") - HEX_SIMPLEX_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEX_SIMPLEX).scale(1)), + HEX_SIMPLEX_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEX_SIMPLEX), NoiseType.HEX_SIMPLEX), @Desc("Hexagonal random-size gradient noise and distorted using Iris styled wispy noise.") - HEX_RANDOM_SIZE_IRIS(rng -> CNG.signature(rng, NoiseType.HEX_RANDOM_SIZE).scale(1)), + HEX_RANDOM_SIZE_IRIS(rng -> CNG.signature(rng, NoiseType.HEX_RANDOM_SIZE), NoiseType.HEX_RANDOM_SIZE), @Desc("Hexagonal random-size gradient noise and distorted using Iris styled wispy noise.") - HEX_RANDOM_SIZE_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEX_RANDOM_SIZE).scale(1)), + HEX_RANDOM_SIZE_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEX_RANDOM_SIZE), NoiseType.HEX_RANDOM_SIZE), @Desc("Hexagonal random-size gradient noise and distorted using Iris styled wispy noise.") - HEX_RANDOM_SIZE_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEX_RANDOM_SIZE).scale(1)), + HEX_RANDOM_SIZE_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEX_RANDOM_SIZE), NoiseType.HEX_RANDOM_SIZE), @Desc("Hexagonal random-size gradient noise and distorted using Iris styled wispy noise.") - HEX_RANDOM_SIZE_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEX_RANDOM_SIZE).scale(1)), + HEX_RANDOM_SIZE_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEX_RANDOM_SIZE), NoiseType.HEX_RANDOM_SIZE), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders. Cells are distorted using Iris styled wispy noise.") - CELLULAR_IRIS(rng -> CNG.signature(rng, NoiseType.CELLULAR)), + CELLULAR_IRIS(rng -> CNG.signature(rng, NoiseType.CELLULAR), NoiseType.CELLULAR), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders. Cells are distorted using Iris styled wispy noise.") - CELLULAR_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.CELLULAR)), + CELLULAR_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.CELLULAR), NoiseType.CELLULAR), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders. Cells are distorted using Iris styled wispy noise.") - CELLULAR_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.CELLULAR)), + CELLULAR_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.CELLULAR), NoiseType.CELLULAR), @Desc("Cellular noise creates the same noise level for cells, changes noise level on cell borders. Cells are distorted using Iris styled wispy noise.") - CELLULAR_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.CELLULAR)), + CELLULAR_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.CELLULAR), NoiseType.CELLULAR), @Desc("Inverse of vascular, height gets to 1.0 as it approaches the center of a cell") - CELLULAR_HEIGHT(rng -> new CNG(rng, NoiseType.CELLULAR_HEIGHT, 1D, 1)), + CELLULAR_HEIGHT(rng -> new CNG(rng, NoiseType.CELLULAR_HEIGHT, 1D, 1), NoiseType.CELLULAR_HEIGHT), @Desc("Inverse of vascular, height gets to 1.0 as it approaches the center of a cell, using the iris style.") - CELLULAR_HEIGHT_IRIS(rng -> CNG.signature(rng, NoiseType.CELLULAR_HEIGHT)), + CELLULAR_HEIGHT_IRIS(rng -> CNG.signature(rng, NoiseType.CELLULAR_HEIGHT), NoiseType.CELLULAR_HEIGHT), @Desc("Inverse of vascular, height gets to 1.0 as it approaches the center of a cell, using the iris style.") - CELLULAR_HEIGHT_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.CELLULAR_HEIGHT)), + CELLULAR_HEIGHT_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.CELLULAR_HEIGHT), NoiseType.CELLULAR_HEIGHT), @Desc("Inverse of vascular, height gets to 1.0 as it approaches the center of a cell, using the iris style.") - CELLULAR_HEIGHT_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.CELLULAR_HEIGHT)), + CELLULAR_HEIGHT_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.CELLULAR_HEIGHT), NoiseType.CELLULAR_HEIGHT), @Desc("Inverse of vascular, height gets to 1.0 as it approaches the center of a cell, using the iris style.") - CELLULAR_HEIGHT_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.CELLULAR_HEIGHT)), + CELLULAR_HEIGHT_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.CELLULAR_HEIGHT), NoiseType.CELLULAR_HEIGHT), @Desc("Vascular noise gets higher as the position nears a cell border. Cells are distorted using Iris styled wispy noise.") - VASCULAR_IRIS(rng -> CNG.signature(rng, NoiseType.VASCULAR)), + VASCULAR_IRIS(rng -> CNG.signature(rng, NoiseType.VASCULAR), NoiseType.VASCULAR), @Desc("Vascular noise gets higher as the position nears a cell border. Cells are distorted using Iris styled wispy noise.") - VASCULAR_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.VASCULAR)), + VASCULAR_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.VASCULAR), NoiseType.VASCULAR), @Desc("Vascular noise gets higher as the position nears a cell border. Cells are distorted using Iris styled wispy noise.") - VASCULAR_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.VASCULAR)), + VASCULAR_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.VASCULAR), NoiseType.VASCULAR), @Desc("Vascular noise gets higher as the position nears a cell border. Cells are distorted using Iris styled wispy noise.") - VASCULAR_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.VASCULAR)), + VASCULAR_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.VASCULAR), NoiseType.VASCULAR), ; private final CNGFactory f; + private final NoiseType type; NoiseStyle(CNGFactory f) { + this(f, NoiseType.SIMPLEX); + } + + NoiseStyle(CNGFactory f, NoiseType type) { this.f = f; + this.type = type; } public ProceduralStream stream(RNG seed) { @@ -555,7 +561,7 @@ public enum NoiseStyle { } public CNG create(RNG seed) { - CNG cng = f.create(seed).bake(); + CNG cng = f.create(seed).bake().scale(type.getCoordinateScale()).bake(); cng.setLeakStyle(this); return cng; } 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 c98d48e12..47bc6036e 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 @@ -19,13 +19,13 @@ package art.arcane.iris.util.project.interpolation; import com.google.common.util.concurrent.AtomicDouble; -import art.arcane.volmlib.util.interpolation.Starcast; import art.arcane.iris.engine.object.NoiseStyle; import art.arcane.volmlib.util.function.NoiseProvider; import art.arcane.volmlib.util.math.RNG; import art.arcane.iris.util.project.noise.CNG; public class IrisInterpolation { + private static final double[][] STARCAST_OFFSETS = createStarcastOffsets(); public static CNG cng = NoiseStyle.SIMPLEX.create(new RNG()); private static final ThreadLocal NOISE_SAMPLE_CACHE_2D = ThreadLocal.withInitial(() -> new NoiseSampleCache2D(64)); private static final ThreadLocal NOISE_BOUNDS_SAMPLE_CACHE_2D = ThreadLocal.withInitial(() -> new NoiseBoundsSampleCache2D(64)); @@ -35,8 +35,8 @@ public class IrisInterpolation { } public static double parametric(double t, double alpha) { - double sqt = Math.pow(t, alpha); - return sqt / (alpha * (sqt - Math.pow(t, alpha - 1)) + 1.0d); + double forward = Math.pow(t, alpha); + return forward / (forward + Math.pow(1D - t, alpha)); } public static float lerpf(float a, float b, float f) { @@ -287,13 +287,17 @@ public class IrisInterpolation { return (int) Math.floor(coord / radius); } - public static double getBilinearNoise(int x, int z, double rad, NoiseProvider n) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); + public static int getRadiusFactor(double coord, double radius) { + return (int) Math.floor(coord / radius); + } + + public static double getBilinearNoise(double x, double z, double rad, NoiseProvider n) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); @@ -307,13 +311,13 @@ public class IrisInterpolation { //@done } - public static double getBilinearBezierNoise(int x, int z, double rad, NoiseProvider n) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); + public static double getBilinearBezierNoise(double x, double z, double rad, NoiseProvider n) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -326,13 +330,13 @@ public class IrisInterpolation { //@done } - public static double getBilinearParametricNoise(int x, int z, double rad, NoiseProvider n, double a) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); + public static double getBilinearParametricNoise(double x, double z, double rad, NoiseProvider n, double a) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -345,13 +349,13 @@ public class IrisInterpolation { //@done } - public static double getBilinearCenterSineNoise(int x, int z, double rad, NoiseProvider n) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); + public static double getBilinearCenterSineNoise(double x, double z, double rad, NoiseProvider n) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -364,17 +368,17 @@ public class IrisInterpolation { //@done } - public static double getBicubicNoise(int x, int z, double rad, NoiseProvider n) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x0 = (int) Math.round((fx - 1) * rad); - int z0 = (int) Math.round((fz - 1) * rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); - int x3 = (int) Math.round((fx + 2) * rad); - int z3 = (int) Math.round((fz + 2) * rad); + public static double getBicubicNoise(double x, double z, double rad, NoiseProvider n) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x0 = (fx - 1) * rad; + double z0 = (fz - 1) * rad; + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; + double x3 = (fx + 2) * rad; + double z3 = (fz + 2) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -399,17 +403,17 @@ public class IrisInterpolation { //@done } - public static double getBicubicBezierNoise(int x, int z, double rad, NoiseProvider n) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x0 = (int) Math.round((fx - 1) * rad); - int z0 = (int) Math.round((fz - 1) * rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); - int x3 = (int) Math.round((fx + 2) * rad); - int z3 = (int) Math.round((fz + 2) * rad); + public static double getBicubicBezierNoise(double x, double z, double rad, NoiseProvider n) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x0 = (fx - 1) * rad; + double z0 = (fz - 1) * rad; + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; + double x3 = (fx + 2) * rad; + double z3 = (fz + 2) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -434,17 +438,17 @@ public class IrisInterpolation { //@done } - public static double getBicubicParametricNoise(int x, int z, double rad, NoiseProvider n, double a) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x0 = (int) Math.round((fx - 1) * rad); - int z0 = (int) Math.round((fz - 1) * rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); - int x3 = (int) Math.round((fx + 2) * rad); - int z3 = (int) Math.round((fz + 2) * rad); + public static double getBicubicParametricNoise(double x, double z, double rad, NoiseProvider n, double a) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x0 = (fx - 1) * rad; + double z0 = (fz - 1) * rad; + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; + double x3 = (fx + 2) * rad; + double z3 = (fz + 2) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -469,29 +473,29 @@ public class IrisInterpolation { //@done } - public static double getHermiteNoise(int x, int z, double rad, NoiseProvider n) { + public static double getHermiteNoise(double x, double z, double rad, NoiseProvider n) { return getHermiteNoise(x, z, rad, n, 0.5, 0); } - public static double getHermiteBezierNoise(int x, int z, double rad, NoiseProvider n) { + public static double getHermiteBezierNoise(double x, double z, double rad, NoiseProvider n) { return getHermiteBezierNoise(x, z, rad, n, 0.5, 0); } - public static double getHermiteParametricNoise(int x, int z, double rad, NoiseProvider n, double a) { + public static double getHermiteParametricNoise(double x, double z, double rad, NoiseProvider n, double a) { return getHermiteParametricNoise(x, z, rad, n, 0.5, 0, a); } - public static double getHermiteNoise(int x, int z, double rad, NoiseProvider n, double t, double b) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x0 = (int) Math.round((fx - 1) * rad); - int z0 = (int) Math.round((fz - 1) * rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); - int x3 = (int) Math.round((fx + 2) * rad); - int z3 = (int) Math.round((fz + 2) * rad); + public static double getHermiteNoise(double x, double z, double rad, NoiseProvider n, double t, double b) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x0 = (fx - 1) * rad; + double z0 = (fz - 1) * rad; + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; + double x3 = (fx + 2) * rad; + double z3 = (fz + 2) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -516,17 +520,17 @@ public class IrisInterpolation { //@done } - public static double getHermiteBezierNoise(int x, int z, double rad, NoiseProvider n, double t, double b) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x0 = (int) Math.round((fx - 1) * rad); - int z0 = (int) Math.round((fz - 1) * rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); - int x3 = (int) Math.round((fx + 2) * rad); - int z3 = (int) Math.round((fz + 2) * rad); + public static double getHermiteBezierNoise(double x, double z, double rad, NoiseProvider n, double t, double b) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x0 = (fx - 1) * rad; + double z0 = (fz - 1) * rad; + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; + double x3 = (fx + 2) * rad; + double z3 = (fz + 2) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -551,17 +555,17 @@ public class IrisInterpolation { //@done } - public static double getHermiteParametricNoise(int x, int z, double rad, NoiseProvider n, double t, double b, double a) { - int fx = getRadiusFactor(x, rad); - int fz = getRadiusFactor(z, rad); - int x0 = (int) Math.round((fx - 1) * rad); - int z0 = (int) Math.round((fz - 1) * rad); - int x1 = (int) Math.round(fx * rad); - int z1 = (int) Math.round(fz * rad); - int x2 = (int) Math.round((fx + 1) * rad); - int z2 = (int) Math.round((fz + 1) * rad); - int x3 = (int) Math.round((fx + 2) * rad); - int z3 = (int) Math.round((fz + 2) * rad); + public static double getHermiteParametricNoise(double x, double z, double rad, NoiseProvider n, double t, double b, double a) { + double fx = Math.floor(x / rad); + double fz = Math.floor(z / rad); + double x0 = (fx - 1) * rad; + double z0 = (fz - 1) * rad; + double x1 = fx * rad; + double z1 = fz * rad; + double x2 = (fx + 1) * rad; + double z2 = (fz + 1) * rad; + double x3 = (fx + 2) * rad; + double z3 = (fz + 2) * rad; double px = rangeScale(0, 1, x1, x2, x); double pz = rangeScale(0, 1, z1, z2, z); //@builder @@ -601,7 +605,7 @@ public class IrisInterpolation { return rad.get(); } - public static double getNoise(InterpolationMethod method, int x, int z, double h, NoiseProvider noise) { + public static double getNoise(InterpolationMethod method, double x, double z, double h, NoiseProvider noise) { if (usesSampleCache(method)) { NoiseSampleCache2D cache = NOISE_SAMPLE_CACHE_2D.get(); if (!cache.isInUse()) { @@ -620,37 +624,37 @@ public class IrisInterpolation { return dispatch(method, x, z, h, noise); } - private static double dispatch(InterpolationMethod method, int x, int z, double h, NoiseProvider n) { + private static double dispatch(InterpolationMethod method, double x, double z, double h, NoiseProvider n) { return switch (method) { case BILINEAR -> getBilinearNoise(x, z, h, n); - case STARCAST_3 -> Starcast.starcast(x, z, h, 3D, n); - case STARCAST_6 -> Starcast.starcast(x, z, h, 6D, n); - case STARCAST_9 -> Starcast.starcast(x, z, h, 9D, n); - case STARCAST_12 -> Starcast.starcast(x, z, h, 12D, n); + case STARCAST_3 -> starcast(x, z, h, 3, n); + case STARCAST_6 -> starcast(x, z, h, 6, n); + case STARCAST_9 -> starcast(x, z, h, 9, n); + case STARCAST_12 -> starcast(x, z, h, 12, n); case BILINEAR_STARCAST_3 -> - Starcast.starcast(x, z, h, 3D, (xx, zz) -> getBilinearNoise((int) xx, (int) zz, h, n)); + starcast(x, z, h, 3, (xx, zz) -> getBilinearNoise(xx, zz, h, n)); case BILINEAR_STARCAST_6 -> - Starcast.starcast(x, z, h, 6D, (xx, zz) -> getBilinearNoise((int) xx, (int) zz, h, n)); + starcast(x, z, h, 6, (xx, zz) -> getBilinearNoise(xx, zz, h, n)); case BILINEAR_STARCAST_9 -> - Starcast.starcast(x, z, h, 9D, (xx, zz) -> getBilinearNoise((int) xx, (int) zz, h, n)); + starcast(x, z, h, 9, (xx, zz) -> getBilinearNoise(xx, zz, h, n)); case BILINEAR_STARCAST_12 -> - Starcast.starcast(x, z, h, 12D, (xx, zz) -> getBilinearNoise((int) xx, (int) zz, h, n)); + starcast(x, z, h, 12, (xx, zz) -> getBilinearNoise(xx, zz, h, n)); case HERMITE_STARCAST_3 -> - Starcast.starcast(x, z, h, 3D, (xx, zz) -> getHermiteNoise((int) xx, (int) zz, h, n, 0D, 0D)); + starcast(x, z, h, 3, (xx, zz) -> getHermiteNoise(xx, zz, h, n, 0D, 0D)); case HERMITE_STARCAST_6 -> - Starcast.starcast(x, z, h, 6D, (xx, zz) -> getHermiteNoise((int) xx, (int) zz, h, n, 0D, 0D)); + starcast(x, z, h, 6, (xx, zz) -> getHermiteNoise(xx, zz, h, n, 0D, 0D)); case HERMITE_STARCAST_9 -> - Starcast.starcast(x, z, h, 9D, (xx, zz) -> getHermiteNoise((int) xx, (int) zz, h, n, 0D, 0D)); + starcast(x, z, h, 9, (xx, zz) -> getHermiteNoise(xx, zz, h, n, 0D, 0D)); case HERMITE_STARCAST_12 -> - Starcast.starcast(x, z, h, 12D, (xx, zz) -> getHermiteNoise((int) xx, (int) zz, h, n, 0D, 0D)); + starcast(x, z, h, 12, (xx, zz) -> getHermiteNoise(xx, zz, h, n, 0D, 0D)); case BILINEAR_BEZIER -> getBilinearBezierNoise(x, z, h, n); case BILINEAR_PARAMETRIC_2 -> getBilinearParametricNoise(x, z, h, n, 2); case BILINEAR_PARAMETRIC_4 -> getBilinearParametricNoise(x, z, h, n, 4); case BILINEAR_PARAMETRIC_1_5 -> getBilinearParametricNoise(x, z, h, n, 1.5); - case BICUBIC -> getBilinearNoise(x, z, h, n); + case BICUBIC -> getBicubicNoise(x, z, h, n); case HERMITE -> getHermiteNoise(x, z, h, n); case HERMITE_TENSE -> getHermiteNoise(x, z, h, n, 0.8D, 0D); - case CATMULL_ROM_SPLINE -> getHermiteNoise(x, z, h, n, 1D, 0D); + case CATMULL_ROM_SPLINE -> getHermiteNoise(x, z, h, n, 0D, 0D); case HERMITE_LOOSE -> getHermiteNoise(x, z, h, n, 0D, 0D); case HERMITE_LOOSE_HALF_NEGATIVE_BIAS -> getHermiteNoise(x, z, h, n, 0D, -0.5D); case HERMITE_LOOSE_HALF_POSITIVE_BIAS -> getHermiteNoise(x, z, h, n, 0D, 0.5D); @@ -660,7 +664,7 @@ public class IrisInterpolation { }; } - public static NoiseBounds getNoiseBounds(InterpolationMethod method, int x, int z, double h, NoiseBoundsProvider noise) { + public static NoiseBounds getNoiseBounds(InterpolationMethod method, double x, double z, double h, NoiseBoundsProvider noise) { NoiseBoundsSampleCache2D cache = NOISE_BOUNDS_SAMPLE_CACHE_2D.get(); if (cache.isInUse()) { // Nested bounds interpolation: a fresh table on the rare nested path, never the @@ -687,6 +691,32 @@ public class IrisInterpolation { } } + private static double[][] createStarcastOffsets() { + double[][] offsets = new double[4][]; + for (int index = 0; index < offsets.length; index++) { + int checks = (index + 1) * 3; + double[] samples = new double[checks * 2]; + for (int sample = 0; sample < checks; sample++) { + double angle = sample * (Math.PI * 2D / checks); + double sin = Math.sin(angle); + double cos = Math.cos(angle); + samples[sample * 2] = cos - sin; + samples[sample * 2 + 1] = sin + cos; + } + offsets[index] = samples; + } + return offsets; + } + + private static double starcast(double x, double z, double radius, int checks, NoiseProvider noise) { + double[] offsets = STARCAST_OFFSETS[checks / 3 - 1]; + double sum = 0D; + for (int sample = 0; sample < offsets.length; sample += 2) { + sum += noise.noise(x + radius * offsets[sample], z + radius * offsets[sample + 1]); + } + return sum / checks; + } + private static boolean usesSampleCache(InterpolationMethod method) { return switch (method) { case BILINEAR_STARCAST_3, 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 9894e8dc7..cdf427cf5 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 @@ -39,6 +39,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.util.IdentityHashMap; import java.util.List; @Data @@ -79,6 +80,7 @@ public class CNG { private transient double signedFractureScale; private transient boolean fastPathStateDirty = true; private transient int coordCacheSalt; + private transient Object coordCacheIdentity = new Object(); public CNG(RNG random) { this(random, 1); @@ -93,7 +95,7 @@ public class CNG { } public CNG(RNG random, NoiseType type, double opacity, int octaves) { - this(random, type.create(random.nextParallelRNG((long) ((1113334944L * opacity) + 12922 + octaves)).lmax()), opacity, octaves); + this(random, type.create(random.nextParallelRNG((long) ((1113334944L * opacity) + 12923)).lmax()), opacity, octaves); } public CNG(RNG random, NoiseGenerator generator, double opacity, int octaves) { @@ -115,8 +117,8 @@ public class CNG { this.injectorMode = InjectorMode.ADD; coordCacheSalt = createCoordCacheSalt(); - if (generator instanceof OctaveNoise) { - ((OctaveNoise) generator).setOctaves(octaves); + if (generator instanceof OctaveNoise octaveNoise) { + octaveNoise.setOctaves(octaves); } refreshFastPathState(); @@ -256,7 +258,7 @@ public class CNG { return this; } - cache = null; + markFastPathStateDirty(); File f = new File(new File(cacheFolder, ".cache"), key + ".cnm"); FloatCache fbc; @@ -306,6 +308,7 @@ public class CNG { } cache = fbc; + coordCacheIdentity = new Object(); return this; } @@ -355,6 +358,16 @@ public class CNG { return this; } + public CNG zoom(double factor) { + if (!Double.isFinite(factor) || factor <= 0D || !Double.isFinite(1D / factor)) { + throw new IllegalArgumentException("Zoom must be positive and finite"); + } + if (factor != 1D) { + zoom(factor, new IdentityHashMap<>()); + } + return this; + } + public CNG patch(double c) { patch = c; markFastPathStateDirty(); @@ -650,46 +663,16 @@ public class CNG { return IrisInterpolation.lerp(min, max, noise); } - private double getNoise(double... dim) { - ensureFastPathState(); - double scale = effectiveScale; - - if (fracture == null || noscale) { - return generator.noise( - (dim.length > 0 ? dim[0] : 0D) * scale, - (dim.length > 1 ? dim[1] : 0D) * scale, - (dim.length > 2 ? dim[2] : 0D) * scale) * opacity; - } - - double x = dim.length > 0 ? dim[0] : 0D; - double y = dim.length > 1 ? dim[1] : 0D; - double z = dim.length > 2 ? dim[2] : 0D; - - if (dim.length > 0) { - x += (fracture.noise(dim) - 0.5) * fscale; - } - - if (dim.length > 1) { - y += (fracture.noise(dim[1], dim[0]) - 0.5) * fscale; - } - - if (dim.length > 2) { - z += (fracture.noise(dim[2], dim[0], dim[1]) - 0.5) * fscale; - } - - return generator.noise(x * scale, y * scale, z * scale) * opacity; - } - private double getNoise(double x) { ensureFastPathState(); double scl = effectiveScale; if (fracture == null || noscale) { - return generator.noise(x * scl, 0D, 0D) * opacity; + return generator.noise(x * scl) * opacity; } double fx = x + ((fracture.noiseFast1D(x) - 0.5D) * fscale); - return generator.noise(fx * scl, 0D, 0D) * opacity; + return generator.noise(fx * scl) * opacity; } private double getNoise(double x, double z) { @@ -697,12 +680,12 @@ public class CNG { double scl = effectiveScale; if (fracture == null || noscale) { - return generator.noise(x * scl, z * scl, 0D) * opacity; + return generator.noise(x * scl, z * scl) * opacity; } double fx = x + ((fracture.noiseFast2D(x, z) - 0.5D) * fscale); double fz = z + ((fracture.noiseFast2D(z, x) - 0.5D) * fscale); - return generator.noise(fx * scl, fz * scl, 0D) * opacity; + return generator.noise(fx * scl, fz * scl) * opacity; } private double getNoise(double x, double y, double z) { @@ -735,8 +718,15 @@ public class CNG { return (noise(dim) * 2) - 1; } - private static boolean isWholeCoordinate(double value) { - return value == Math.rint(value); + private boolean isCachedCoordinate(double x, double z) { + return cache != null + && x >= 0D && x < cache.getWidth() + && z >= 0D && z < cache.getHeight() + && x == (int) x && z == (int) z; + } + + private double getCachedNoise(double x, double z) { + return cache.get((int) z * cache.getWidth() + (int) x); } private double applyPost(double n, double x) { @@ -860,57 +850,12 @@ public class CNG { } public double noise(double... dim) { - if (dim.length == 1) { - return noise(dim[0]); - } - - if (dim.length == 2) { - return noise(dim[0], dim[1]); - } - - if (dim.length == 3) { - return noise(dim[0], dim[1], dim[2]); - } - - double n = getNoise(dim); - n = power != 1D ? (n < 0 ? -Math.pow(Math.abs(n), power) : Math.pow(n, power)) : n; - double m = 1; - if (children == null) { - return (n - down + up) * patch; - } - - for (CNG i : children) { - double source = n; - double value = i.noise(dim); - switch (injectorMode) { - case ADD -> { - n = source + value; - m += 1D; - } - case SRC_SUBTRACT -> { - n = source - value < 0D ? 0D : source - value; - m -= 1D; - } - case DST_SUBTRACT -> { - n = value - source < 0D ? 0D : source - value; - m -= 1D; - } - case MULTIPLY -> n = source * value; - case MAX -> n = Math.max(source, value); - case MIN -> n = Math.min(source, value); - case SRC_MOD -> n = source % value; - case SRC_POW -> n = Math.pow(source, value); - case DST_MOD -> n = value % source; - case DST_POW -> n = Math.pow(value, source); - case CUSTOM -> { - double[] combined = injector.combine(source, value); - n = combined[0]; - m += combined[1]; - } - } - } - - return ((n / m) - down + up) * patch; + return switch (dim.length) { + case 1 -> noise(dim[0]); + case 2 -> noise(dim[0], dim[1]); + case 3 -> noise(dim[0], dim[1], dim[2]); + default -> throw new IllegalArgumentException("Noise requires one, two, or three coordinates"); + }; } public double noise(double x) { @@ -922,16 +867,16 @@ public class CNG { } public double noise(double x, double z) { - if (cache != null && isWholeCoordinate(x) && isWholeCoordinate(z)) { - return cache.get((int) x, (int) z); + if (isCachedCoordinate(x, z)) { + return getCachedNoise(x, z); } return applyPost(getNoise(x, z), x, z); } public double noiseFast2D(double x, double z) { - if (cache != null && isWholeCoordinate(x) && isWholeCoordinate(z)) { - return cache.get((int) x, (int) z); + if (isCachedCoordinate(x, z)) { + return getCachedNoise(x, z); } if (isIdentityPostFastPath()) { @@ -963,12 +908,13 @@ public class CNG { long kx = Double.doubleToRawLongBits(x); long kz = Double.doubleToRawLongBits(z); CoordCache cc = COORD_CACHE_2D.get(); + Object identity = coordCacheIdentity(); int slot = coordSlot(kx, kz, coordCacheSalt()); - if (cc.owner[slot] == this && cc.kx[slot] == kx && cc.kz[slot] == kz) { + if (cc.owner[slot] == identity && cc.kx[slot] == kx && cc.kz[slot] == kz) { return cc.value[slot]; } double computed = computeNoiseFastSigned2D(x, z); - cc.owner[slot] = this; + cc.owner[slot] = identity; cc.kx[slot] = kx; cc.kz[slot] = kz; cc.value[slot] = computed; @@ -976,8 +922,8 @@ public class CNG { } private double computeNoiseFastSigned2D(double x, double z) { - if (cache != null && isWholeCoordinate(x) && isWholeCoordinate(z)) { - return (cache.get((int) x, (int) z) * 2D) - 1D; + if (isCachedCoordinate(x, z)) { + return (getCachedNoise(x, z) * 2D) - 1D; } if (hasIdentitySignedFastPath()) { @@ -1014,12 +960,13 @@ public class CNG { long ky = Double.doubleToRawLongBits(y); long kz = Double.doubleToRawLongBits(z); CoordCache3D cc = COORD_CACHE_3D.get(); + Object identity = coordCacheIdentity(); int slot = coordSlot(kx ^ Long.rotateLeft(ky, 21), kz, coordCacheSalt()); - if (cc.owner[slot] == this && cc.kx[slot] == kx && cc.ky[slot] == ky && cc.kz[slot] == kz) { + if (cc.owner[slot] == identity && cc.kx[slot] == kx && cc.ky[slot] == ky && cc.kz[slot] == kz) { return cc.value[slot]; } double computed = hasIdentitySignedFastPath() ? getSignedNoise(x, y, z) : (noiseFast3D(x, y, z) * 2D) - 1D; - cc.owner[slot] = this; + cc.owner[slot] = identity; cc.kx[slot] = kx; cc.ky[slot] = ky; cc.kz[slot] = kz; @@ -1034,7 +981,15 @@ public class CNG { } public CNG oct(int octaves) { + if (oct == octaves) { + return this; + } + oct = octaves; + if (generator instanceof OctaveNoise octaveNoise) { + octaveNoise.setOctaves(octaves); + } + markFastPathStateDirty(); return this; } @@ -1056,6 +1011,23 @@ public class CNG { return identitySignedFastPath; } + private void zoom(double factor, IdentityHashMap visited) { + if (visited.put(this, Boolean.TRUE) != null) { + return; + } + bakedScale /= factor; + fscale *= factor; + if (fracture != null) { + fracture.zoom(factor, visited); + } + if (children != null) { + for (CNG child : children) { + child.zoom(factor, visited); + } + } + markFastPathStateDirty(); + } + private void ensureFastPathState() { if (fastPathStateDirty) { refreshFastPathState(); @@ -1064,6 +1036,16 @@ public class CNG { private void markFastPathStateDirty() { fastPathStateDirty = true; + cache = null; + coordCacheIdentity = new Object(); + } + + private Object coordCacheIdentity() { + if (coordCacheIdentity == null) { + coordCacheIdentity = new Object(); + } + + return coordCacheIdentity; } private int coordCacheSalt() { @@ -1112,12 +1094,12 @@ public class CNG { CNG localFracture = fracture; if (localFracture == null || noscale) { - return signedWithOpacity(localGenerator.noiseSigned(x * scl, z * scl, 0D)); + return signedWithOpacity(localGenerator.noiseSigned(x * scl, z * scl)); } 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)); + return signedWithOpacity(localGenerator.noiseSigned(fx * scl, fz * scl)); } private double getSignedNoise(double x, double y, double z) { diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CellHeightNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/CellHeightNoise.java index f478bfc90..eed4f2729 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/CellHeightNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CellHeightNoise.java @@ -32,7 +32,7 @@ public class CellHeightNoise implements NoiseGenerator { } private double filter(double noise) { - return M.clip(1D - ((noise / 2D) + 0.5D), 0D, 1D); + return M.clip((noise / 2D) + 0.5D, 0D, 1D); } @Override diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CellularNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/CellularNoise.java index 2218229a5..cfee3c581 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/CellularNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CellularNoise.java @@ -21,27 +21,27 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.math.RNG; public class CellularNoise implements NoiseGenerator { - private final FastNoise n; + private final FastNoiseDouble n; public CellularNoise(long seed) { - this.n = new FastNoise(new RNG(seed).imax()); - n.SetNoiseType(FastNoise.NoiseType.Cellular); - n.SetCellularReturnType(FastNoise.CellularReturnType.CellValue); - n.SetCellularDistanceFunction(FastNoise.CellularDistanceFunction.Natural); + this.n = new FastNoiseDouble(new RNG(seed).lmax()); + n.setNoiseType(FastNoiseDouble.NoiseType.Cellular); + n.setCellularReturnType(FastNoiseDouble.CellularReturnType.CellValue); + n.setCellularDistanceFunction(FastNoiseDouble.CellularDistanceFunction.Natural); } @Override public double noise(double x) { - return (n.GetCellular((float) x, 0) / 2D) + 0.5D; + return (n.GetCellular(x, 0) / 2D) + 0.5D; } @Override public double noise(double x, double z) { - return (n.GetCellular((float) x, (float) z) / 2D) + 0.5D; + return (n.GetCellular(x, z) / 2D) + 0.5D; } @Override public double noise(double x, double y, double z) { - return (n.GetCellular((float) x, (float) y, (float) z) / 2D) + 0.5D; + return (n.GetCellular(x, y, z) / 2D) + 0.5D; } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CloverNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/CloverNoise.java index 170cf6d5c..8f9ae0808 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/CloverNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CloverNoise.java @@ -39,10 +39,6 @@ public class CloverNoise implements NoiseGenerator { @Override public double noise(double x, double y, double z) { - if (z == 0) { - return n2.noise(x, y); - } - return n3.noise(x, y, z); } @@ -129,8 +125,6 @@ public class CloverNoise implements NoiseGenerator { Vector2 p_floor = p.floor(); Vector2 c_11 = offset(p_floor); - Vector2 c_10 = offset(p_floor.add(0, -1)); - Vector2 c_12 = offset(p_floor.add(0, 1)); Vector2 c_01 = offset(p_floor.add(-1, 0)); Vector2 c_21 = offset(p_floor.add(1, 0)); @@ -143,6 +137,7 @@ public class CloverNoise implements NoiseGenerator { Vector2 a, c, d; if (side_nx < 0 && p.x < c_11.x || side_px > 0 && p.x > c_11.x) { + Vector2 c_12 = offset(p_floor.add(0, 1)); double side_py = m_p_c11.sub(d_p_c11.mult(c_12)).ymx(); if (side_py > 0) { @@ -155,6 +150,7 @@ public class CloverNoise implements NoiseGenerator { d = new Vector2(1, 1); } } else { + Vector2 c_10 = offset(p_floor.add(0, -1)); double side_ny = m_p_c11.sub(d_p_c11.mult(c_10)).ymx(); if (side_ny > 0) { @@ -235,6 +231,7 @@ public class CloverNoise implements NoiseGenerator { * @return The value of noise, from 0 to 1. */ public double fractalNoise(Vector2 p, int iterations) { + iterations = Math.max(1, Math.min(16, iterations)); double total = 0; double weight = 1; double div = 0; @@ -382,6 +379,18 @@ public class CloverNoise implements NoiseGenerator { private static final long HASH_M = 0x1000000000000L; private static final double POINT_SPREAD = 0.2; private static final double CURL_DX = 0.0001; + private static final double[] HEIGHT_SINES = new double[1000]; + private static final double[] HEIGHT_COSINES = new double[1000]; + private static final ThreadLocal SAMPLE_VERTICES = ThreadLocal.withInitial(Noise3D::createSampleVertices); + + static { + for (int index = 0; index < HEIGHT_SINES.length; index++) { + double height = (((index + 0.5D) / 100D) % 1D - 0.5D) * Math.PI / 2D; + HEIGHT_SINES[index] = Math.sin(height); + HEIGHT_COSINES[index] = Math.cos(height); + } + } + private final long seed; public Noise3D(long seed) { @@ -392,6 +401,14 @@ public class CloverNoise implements NoiseGenerator { this(System.currentTimeMillis()); } + private static Vector3[] createSampleVertices() { + Vector3[] vertices = new Vector3[21]; + for (int index = 0; index < vertices.length; index++) { + vertices[index] = new Vector3(); + } + return vertices; + } + private long doHash(long input, long seed) { input += seed; @@ -433,27 +450,23 @@ public class CloverNoise implements NoiseGenerator { return (double) hash / HASH_M; } - private Vector3 offset(Vector3 position) { - return offset(position.x, position.y, position.z); + private Vector3 offset(Vector3 destination, Vector3 position, double xOffset, double yOffset, double zOffset) { + return offset(destination, position.x + xOffset, position.y + yOffset, position.z + zOffset); } - private Vector3 offset(Vector3 position, double xOffset, double yOffset, double zOffset) { - return offset(position.x + xOffset, position.y + yOffset, position.z + zOffset); - } - - private Vector3 offset(double positionX, double positionY, double positionZ) { + private Vector3 offset(Vector3 destination, double positionX, double positionY, double positionZ) { double hash = hash(positionX, positionY, positionZ); double theta = hash * Math.PI * 2000; - double height = (((Math.floor(hash * 1000) + 0.5) / 100) % 1 - 0.5) * Math.PI / 2; + int heightIndex = (int) (hash * 1000D); double layer = Math.floor(hash * 10 + 1) / 10; - double cosHeight = Math.cos(height); + double cosHeight = HEIGHT_COSINES[heightIndex]; double offsetX = Math.sin(theta) * cosHeight * layer + 0.5; - double offsetY = Math.sin(height) * layer + 0.5; + double offsetY = HEIGHT_SINES[heightIndex] * layer + 0.5; double offsetZ = Math.cos(theta) * cosHeight * layer + 0.5; - return new Vector3( - positionX + (offsetX * (POINT_SPREAD * 2) + (0.5 - POINT_SPREAD)), - positionY + (offsetY * (POINT_SPREAD * 2) + (0.5 - POINT_SPREAD)), - positionZ + (offsetZ * (POINT_SPREAD * 2) + (0.5 - POINT_SPREAD))); + destination.x = positionX + (offsetX * (POINT_SPREAD * 2) + (0.5 - POINT_SPREAD)); + destination.y = positionY + (offsetY * (POINT_SPREAD * 2) + (0.5 - POINT_SPREAD)); + destination.z = positionZ + (offsetZ * (POINT_SPREAD * 2) + (0.5 - POINT_SPREAD)); + return destination; } private boolean boundary(int permutation, Vector3 p, Vector3 c_00, Vector3 c_10, Vector3 c_20, Vector3 c_01, Vector3 c_11, Vector3 c_21, Vector3 c_02, Vector3 c_12, Vector3 c_22) { @@ -565,27 +578,34 @@ public class CloverNoise implements NoiseGenerator { * @return The value of noise, from 0 to 1. */ public double noise(Vector3 p) { - Vector3 p_floor = p.floor(); + return sample(p, SAMPLE_VERTICES.get()); + } - Vector3 c_111 = offset(p_floor); - Vector3 c_100 = offset(p_floor, 0, -1, -1); - Vector3 c_010 = offset(p_floor, -1, 0, -1); - Vector3 c_110 = offset(p_floor, 0, 0, -1); - Vector3 c_210 = offset(p_floor, 1, 0, -1); - Vector3 c_120 = offset(p_floor, 0, 1, -1); - Vector3 c_001 = offset(p_floor, -1, -1, 0); - Vector3 c_101 = offset(p_floor, 0, -1, 0); - Vector3 c_201 = offset(p_floor, 1, -1, 0); - Vector3 c_011 = offset(p_floor, -1, 0, 0); - Vector3 c_211 = offset(p_floor, 1, 0, 0); - Vector3 c_021 = offset(p_floor, -1, 1, 0); - Vector3 c_121 = offset(p_floor, 0, 1, 0); - Vector3 c_221 = offset(p_floor, 1, 1, 0); - Vector3 c_102 = offset(p_floor, 0, -1, 1); - Vector3 c_012 = offset(p_floor, -1, 0, 1); - Vector3 c_112 = offset(p_floor, 0, 0, 1); - Vector3 c_212 = offset(p_floor, 1, 0, 1); - Vector3 c_122 = offset(p_floor, 0, 1, 1); + private double sample(Vector3 p, Vector3[] vertices) { + Vector3 p_floor = vertices[20]; + p_floor.x = Math.floor(p.x); + p_floor.y = Math.floor(p.y); + p_floor.z = Math.floor(p.z); + + Vector3 c_111 = offset(vertices[0], p_floor.x, p_floor.y, p_floor.z); + Vector3 c_100 = offset(vertices[1], p_floor, 0, -1, -1); + Vector3 c_010 = offset(vertices[2], p_floor, -1, 0, -1); + Vector3 c_110 = offset(vertices[3], p_floor, 0, 0, -1); + Vector3 c_210 = offset(vertices[4], p_floor, 1, 0, -1); + Vector3 c_120 = offset(vertices[5], p_floor, 0, 1, -1); + Vector3 c_001 = offset(vertices[6], p_floor, -1, -1, 0); + Vector3 c_101 = offset(vertices[7], p_floor, 0, -1, 0); + Vector3 c_201 = offset(vertices[8], p_floor, 1, -1, 0); + Vector3 c_011 = offset(vertices[9], p_floor, -1, 0, 0); + Vector3 c_211 = offset(vertices[10], p_floor, 1, 0, 0); + Vector3 c_021 = offset(vertices[11], p_floor, -1, 1, 0); + Vector3 c_121 = offset(vertices[12], p_floor, 0, 1, 0); + Vector3 c_221 = offset(vertices[13], p_floor, 1, 1, 0); + Vector3 c_102 = offset(vertices[14], p_floor, 0, -1, 1); + Vector3 c_012 = offset(vertices[15], p_floor, -1, 0, 1); + Vector3 c_112 = offset(vertices[16], p_floor, 0, 0, 1); + Vector3 c_212 = offset(vertices[17], p_floor, 1, 0, 1); + Vector3 c_122 = offset(vertices[18], p_floor, 0, 1, 1); boolean x_bound = boundary(1, p, c_100, c_110, c_120, c_101, c_111, c_121, c_102, c_112, c_122); boolean y_bound = boundary(2, p, c_010, c_110, c_210, c_011, c_111, c_211, c_012, c_112, c_212); @@ -596,7 +616,7 @@ public class CloverNoise implements NoiseGenerator { if (x_bound) { if (y_bound) { if (z_bound) { - a = offset(p_floor, -1, -1, -1); + a = offset(vertices[19], p_floor, -1, -1, -1); b = c_001; c = c_010; d = c_011; @@ -606,7 +626,7 @@ public class CloverNoise implements NoiseGenerator { h = c_111; } else { a = c_001; - b = offset(p_floor, -1, -1, 1); + b = offset(vertices[19], p_floor, -1, -1, 1); c = c_011; d = c_012; e = c_101; @@ -618,7 +638,7 @@ public class CloverNoise implements NoiseGenerator { if (z_bound) { a = c_010; b = c_011; - c = offset(p_floor, -1, 1, -1); + c = offset(vertices[19], p_floor, -1, 1, -1); d = c_021; e = c_110; f = c_111; @@ -628,7 +648,7 @@ public class CloverNoise implements NoiseGenerator { a = c_011; b = c_012; c = c_021; - d = offset(p_floor, -1, 1, 1); + d = offset(vertices[19], p_floor, -1, 1, 1); e = c_111; f = c_112; g = c_121; @@ -642,7 +662,7 @@ public class CloverNoise implements NoiseGenerator { b = c_101; c = c_110; d = c_111; - e = offset(p_floor, 1, -1, -1); + e = offset(vertices[19], p_floor, 1, -1, -1); f = c_201; g = c_210; h = c_211; @@ -652,7 +672,7 @@ public class CloverNoise implements NoiseGenerator { c = c_111; d = c_112; e = c_201; - f = offset(p_floor, 1, -1, 1); + f = offset(vertices[19], p_floor, 1, -1, 1); g = c_211; h = c_212; } @@ -664,7 +684,7 @@ public class CloverNoise implements NoiseGenerator { d = c_121; e = c_210; f = c_211; - g = offset(p_floor, 1, 1, -1); + g = offset(vertices[19], p_floor, 1, 1, -1); h = c_221; } else { a = c_111; @@ -674,7 +694,7 @@ public class CloverNoise implements NoiseGenerator { e = c_211; f = c_212; g = c_221; - h = offset(p_floor, 1, 1, 1); + h = offset(vertices[19], p_floor, 1, 1, 1); } } } @@ -783,6 +803,7 @@ public class CloverNoise implements NoiseGenerator { * @return The value of noise, from 0 to 1. */ public double fractalNoise(Vector3 p, int iterations) { + iterations = Math.max(1, Math.min(16, iterations)); double total = 0; double weight = 1; double div = 0; @@ -980,6 +1001,9 @@ public class CloverNoise implements NoiseGenerator { public Vector2 normalize() { double len2 = x * x + y * y; + if (len2 == 0D) { + return new Vector2(0D, 0D); + } double len = Math.sqrt(len2); return new Vector2(x / len, y / len); @@ -1092,6 +1116,9 @@ public class CloverNoise implements NoiseGenerator { public Vector3 normalize() { double len2 = x * x + y * y + z * z; + if (len2 == 0D) { + return new Vector3(0D, 0D, 0D); + } double len = Math.sqrt(len2); return new Vector3(x / len, y / len, z / len); diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/FastNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/FastNoise.java index 3e97fe38a..bd21d37c4 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/FastNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/FastNoise.java @@ -76,7 +76,7 @@ public class FastNoise { } private static int FastFloor(float f) { - return (f >= 0 ? (int) f : (int) f - 1); + return (int) Math.floor(f); } private static int FastRound(float f) { @@ -259,7 +259,7 @@ public class FastNoise { // Sets octave count for all fractal noise types // Default: 3 public void SetFractalOctaves(int octaves) { - m_octaves = octaves; + m_octaves = Math.max(1, Math.min(16, octaves)); CalculateFractalBounding(); } @@ -1515,7 +1515,7 @@ public class FastNoise { x *= m_frequency; y *= m_frequency; - return SingleCubic(0, x, y); + return SingleCubic(m_seed, x, y); } private float SingleCubic(int seed, float x, float y) { @@ -2194,4 +2194,4 @@ public class FastNoise { } } -} \ No newline at end of file +} 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 0a0d53c56..5b0362d41 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 @@ -82,7 +82,7 @@ public class FastNoiseDouble { } private static long fastFloor(double f) { - return (f >= 0 ? (long) f : (long) f - 1); + return (long) Math.floor(f); } private static long fastRound(double f) { @@ -289,7 +289,7 @@ public class FastNoiseDouble { // Sets octave count for all fractal noise types // Default: 3 public void setFractalOctaves(long octaves) { - m_octaves = octaves; + m_octaves = Math.max(1, Math.min(16, octaves)); calculateFractalBounding(); } @@ -1453,7 +1453,7 @@ public class FastNoiseDouble { x *= m_frequency; y *= m_frequency; - return SingleCubic(0, x, y); + return SingleCubic(m_seed, x, y); } private double SingleCubic(long seed, double x, double y) { @@ -1676,14 +1676,11 @@ public class FastNoiseDouble { break; } - return switch (m_cellularReturnType) { - case Distance2 -> distance2 - 1; - case Distance2Add -> distance2 + distance - 1; - case Distance2Sub -> distance2 - distance - 1; - case Distance2Mul -> distance2 * distance - 1; - case Distance2Div -> distance / distance2 - 1; - default -> 0; - }; + double boundary = 1.55D - Math.max(Math.abs(x - xr), Math.max(Math.abs(y - yr), Math.abs(z - zr))); + if (cellularDistance(boundary, 0D, 0D) < distance2) { + return refineCellular2Edge(x, y, z, xr, yr, zr, distance, distance2); + } + return cellular2EdgeResult(distance, distance2); } public double GetCellular(double x, double y) { @@ -1856,6 +1853,93 @@ public class FastNoiseDouble { break; } + double boundary = 1.55D - Math.max(Math.abs(x - xr), Math.abs(y - yr)); + if (cellularDistance(boundary, 0D, 0D) < distance2) { + return refineCellular2Edge(x, y, xr, yr, distance, distance2); + } + return cellular2EdgeResult(distance, distance2); + } + + private double refineCellular2Edge(double x, double y, long xr, long yr, double distance, double distance2) { + for (int dx = -2; dx <= 2; dx++) { + long xi = xr + dx; + double baseX = xi - x; + double boundX = Math.max(0D, Math.abs(baseX) - 0.45D); + if (cellularDistance(boundX, 0D, 0D) >= distance2) { + continue; + } + for (int dy = -2; dy <= 2; dy++) { + if (Math.abs(dx) < 2 && Math.abs(dy) < 2) { + continue; + } + long yi = yr + dy; + double baseY = yi - y; + double boundY = Math.max(0D, Math.abs(baseY) - 0.45D); + if (cellularDistance(boundX, boundY, 0D) >= distance2) { + continue; + } + int index = (int) hash2D(m_seed, xi, yi) & 255; + double candidate = cellularDistance(baseX + CELL_2D_X[index], baseY + CELL_2D_Y[index], 0D); + if (candidate < distance) { + distance2 = distance; + distance = candidate; + } else if (candidate < distance2) { + distance2 = candidate; + } + } + } + return cellular2EdgeResult(distance, distance2); + } + + private double refineCellular2Edge(double x, double y, double z, long xr, long yr, long zr, double distance, double distance2) { + int radius = m_cellularDistanceFunction == CellularDistanceFunction.Manhattan ? 3 : 2; + for (int dx = -radius; dx <= radius; dx++) { + long xi = xr + dx; + double baseX = xi - x; + double boundX = Math.max(0D, Math.abs(baseX) - 0.45D); + if (cellularDistance(boundX, 0D, 0D) >= distance2) { + continue; + } + for (int dy = -radius; dy <= radius; dy++) { + long yi = yr + dy; + double baseY = yi - y; + double boundY = Math.max(0D, Math.abs(baseY) - 0.45D); + if (cellularDistance(boundX, boundY, 0D) >= distance2) { + continue; + } + for (int dz = -radius; dz <= radius; dz++) { + if (Math.abs(dx) < 2 && Math.abs(dy) < 2 && Math.abs(dz) < 2) { + continue; + } + long zi = zr + dz; + double baseZ = zi - z; + double boundZ = Math.max(0D, Math.abs(baseZ) - 0.45D); + if (cellularDistance(boundX, boundY, boundZ) >= distance2) { + continue; + } + int index = (int) hash3D(m_seed, xi, yi, zi) & 255; + double candidate = cellularDistance(baseX + CELL_3D_X[index], baseY + CELL_3D_Y[index], baseZ + CELL_3D_Z[index]); + if (candidate < distance) { + distance2 = distance; + distance = candidate; + } else if (candidate < distance2) { + distance2 = candidate; + } + } + } + } + return cellular2EdgeResult(distance, distance2); + } + + private double cellularDistance(double x, double y, double z) { + return switch (m_cellularDistanceFunction) { + case Euclidean -> x * x + y * y + z * z; + case Manhattan -> Math.abs(x) + Math.abs(y) + Math.abs(z); + case Natural -> (Math.abs(x) + Math.abs(y) + Math.abs(z)) + (x * x + y * y + z * z); + }; + } + + private double cellular2EdgeResult(double distance, double distance2) { return switch (m_cellularReturnType) { case Distance2 -> distance2 - 1; case Distance2Add -> distance2 + distance - 1; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/FractalBillowPerlinNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/FractalBillowPerlinNoise.java index 65e205fbf..d6980b599 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/FractalBillowPerlinNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/FractalBillowPerlinNoise.java @@ -25,6 +25,7 @@ public class FractalBillowPerlinNoise implements NoiseGenerator, OctaveNoise { public FractalBillowPerlinNoise(long seed) { this.n = new FastNoiseDouble(new RNG(seed).lmax()); + n.setLongerp(FastNoiseDouble.Longerp.Qulongic); n.setFractalOctaves(1); n.setFractalType(FractalType.Billow); } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/FractalCubicNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/FractalCubicNoise.java index 65ec7e0f3..bb8d9b846 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/FractalCubicNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/FractalCubicNoise.java @@ -20,11 +20,12 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.math.RNG; import art.arcane.iris.util.project.noise.FastNoiseDouble.FractalType; -public class FractalCubicNoise implements NoiseGenerator { +public class FractalCubicNoise implements NoiseGenerator, OctaveNoise { private final FastNoiseDouble n; public FractalCubicNoise(long seed) { this.n = new FastNoiseDouble(new RNG(seed).lmax()); + n.setFractalOctaves(1); n.setFractalType(FractalType.Billow); } @@ -46,4 +47,9 @@ public class FractalCubicNoise implements NoiseGenerator { public double noise(double x, double y, double z) { return f(n.GetCubicFractal(x, y, z)); } + + @Override + public void setOctaves(int octaves) { + n.setFractalOctaves(octaves); + } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/FractalRigidMultiSimplexNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/FractalRigidMultiSimplexNoise.java index 896091156..d823d9da8 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/FractalRigidMultiSimplexNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/FractalRigidMultiSimplexNoise.java @@ -30,7 +30,7 @@ public class FractalRigidMultiSimplexNoise implements NoiseGenerator, OctaveNois } public double f(double v) { - return (v / 2D) + 0.5D; + return 1D + (v - 1D) * n.getFractalBounding(); } @Override 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 77542bb50..76571385e 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 @@ -2,294 +2,89 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.math.RNG; -public class HexJamesNoise implements NoiseGenerator, OctaveNoise { - private static final double SQRT_3_OVER_3 = Math.sqrt(3.0) / 3.0; - private static final double TWO_OVER_THREE = 2.0 / 3.0; - private static final double SQRT_3 = Math.sqrt(3.0); - private static final double THREE_OVER_TWO = 1.5D; - private static final double ONE_THIRD = 1D / 3D; - private static final double TWO_THIRDS = 2D / 3D; +public class HexJamesNoise extends HexagonalNoise implements OctaveNoise { private static final int MAX_DEPTH = 7; - private static final double HEAT_SCALE = 0.31D; + private static final double HEAT_SCALE = 18D; private static final double LARGE_CONTINUE = 0.26D; private static final double SMALL_CONTINUE = 0.79D; private static final double CENTER_CONTINUE = 0.58D; - private static final long CONST_X = 0x9E3779B97F4A7C15L; - private static final long CONST_Z = 0xC2B2AE3D27D4EB4FL; - private static final long[][] CHILD_DIRECTIONS = new long[][]{ - {1L, 0L}, - {1L, -1L}, - {0L, -1L}, - {-1L, 0L}, - {-1L, 1L}, - {0L, 1L} - }; - private static final ThreadLocal SCRATCH = ThreadLocal.withInitial(HexScratch::new); private final long seed; - private final SimplexNoise heatSimplex; - private int octaves; + private final SimplexNoise heat; public HexJamesNoise(long seed) { RNG rng = new RNG(seed); this.seed = rng.lmax(); - this.heatSimplex = new SimplexNoise(rng.nextParallelRNG(877L).lmax()); - this.octaves = 1; + this.heat = new SimplexNoise(rng.nextParallelRNG(877L).lmax()); } - private double clip(double value) { - if (value < 0D) { - return 0D; - } - - if (value > 1D) { - return 1D; - } - - return value; + @Override + public void setOctaves(int octaves) { + heat.setOctaves(octaves); } - private long mix(long input) { - input = (input ^ (input >>> 33)) * 0xff51afd7ed558ccdL; - input = (input ^ (input >>> 33)) * 0xc4ceb9fe1a85ec53L; - return input ^ (input >>> 33); - } + @Override + protected double sample(double x, double y, double z) { + double qWorld = (SQRT_3_OVER_3 * x) - (z / 3D); + double rWorld = TWO_OVER_THREE * z; + long rootQ = roundQ(qWorld, rWorld); + long rootR = roundR(qWorld, rWorld); + double centerQ = rootQ; + double centerR = rootR; + double radius = 1D; + double localQ = qWorld - centerQ; + double localR = rWorld - centerR; + long nodeHash = mix(seed ^ (rootQ * CONST_X) ^ (rootR * CONST_Z)); + int level = 0; - private double hashToUnit(long value) { - long normalized = mix(value) & Long.MAX_VALUE; - return normalized / (double) Long.MAX_VALUE; - } + while (level < MAX_DEPTH) { + int childIndex = pickChildIndex(localQ, localR); + if (childIndex < 0) { + break; + } - private double random01(long nodeHash, int salt) { - long mixed = nodeHash ^ (CONST_X * (salt + 1L)) ^ (CONST_Z * (salt + 7L)); - return hashToUnit(mixed); - } + double childQ = CHILD_Q[childIndex]; + double childR = CHILD_R[childIndex]; + centerQ += childQ * radius; + centerR += childR * radius; + radius *= ONE_THIRD; + localQ = (localQ - childQ) * 3D; + localR = (localR - childR) * 3D; + nodeHash = mix(nodeHash ^ (CONST_X * (childIndex + 11L)) ^ (CONST_Z * (level + 1L))); + level++; - private long nextNodeHash(long nodeHash, int childIndex, int level) { - long mixed = nodeHash ^ (CONST_X * (childIndex + 11L)) ^ (CONST_Z * (level + 1L)); - return mix(mixed); - } - - private long[] roundAxial(double q, double r) { - double cubeX = q; - double cubeZ = r; - double cubeY = -cubeX - cubeZ; - long roundedX = Math.round(cubeX); - long roundedY = Math.round(cubeY); - long roundedZ = Math.round(cubeZ); - double xDiff = Math.abs(roundedX - cubeX); - double yDiff = Math.abs(roundedY - cubeY); - double zDiff = Math.abs(roundedZ - cubeZ); - - if (xDiff > yDiff && xDiff > zDiff) { - roundedX = -roundedY - roundedZ; - } else if (yDiff > zDiff) { - roundedY = -roundedX - roundedZ; - } else { - roundedZ = -roundedX - roundedY; - } - - return new long[]{roundedX, roundedZ}; - } - - private double hexDistance(double q, double r, double centerQ, double centerR) { - double deltaQ = q - centerQ; - double deltaR = r - centerR; - double deltaY = -deltaQ - deltaR; - return Math.max(Math.abs(deltaQ), Math.max(Math.abs(deltaR), Math.abs(deltaY))); - } - - private double axialX(double q, double r) { - return SQRT_3 * (q + (r * 0.5D)); - } - - private double axialZ(double r) { - return THREE_OVER_TWO * r; - } - - private double simplexField(double x, double z, double scale) { - if (octaves <= 1) { - return heatSimplex.noise(x * scale, z * scale); - } - - double frequency = 1D; - double amplitude = 1D; - double total = 0D; - double max = 0D; - - for (int i = 0; i < octaves; i++) { - total += heatSimplex.noise((x * scale) * frequency, (z * scale) * frequency) * amplitude; - max += amplitude; - frequency *= 2D; - amplitude *= 0.5D; - } - - if (max == 0D) { - return 0.5D; - } - - return clip(total / max); - } - - private double cellHeat(double centerQ, double centerR, long nodeHash, int level) { - double x = axialX(centerQ, centerR); - double z = axialZ(centerR); - double scale = HEAT_SCALE * (1D + (level * 0.24D)); - double simplexValue = simplexField(x, z, scale); - double randomValue = random01(nodeHash, 31 + level); - return clip((simplexValue * 0.82D) + (randomValue * 0.18D)); - } - - private int rotationForNode(long nodeHash) { - int rotation = (int) Math.floor(random01(nodeHash, 3) * 6D); - if (rotation < 0) { - rotation = 0; - } - - if (rotation > 5) { - rotation = 5; - } - - return rotation; - } - - private int parityForNode(long nodeHash) { - return random01(nodeHash, 5) >= 0.5D ? 1 : 0; - } - - private int ringIndexForDirection(int direction, int rotation) { - int ring = direction - rotation; - - while (ring < 0) { - ring += 6; - } - - while (ring >= 6) { - ring -= 6; - } - - return ring; - } - - private int pickChildIndex(double localQ, double localR, double[] centersQ, double[] centersR) { - int bestIndex = -1; - double bestDistance = Double.POSITIVE_INFINITY; - - centersQ[0] = 0D; - centersR[0] = 0D; - double centerDistance = hexDistance(localQ, localR, 0D, 0D) / ONE_THIRD; - - if (centerDistance <= 1D && centerDistance < bestDistance) { - bestDistance = centerDistance; - bestIndex = 0; - } - - for (int i = 0; i < 6; i++) { - int index = i + 1; - double centerQ = CHILD_DIRECTIONS[i][0] * TWO_THIRDS; - double centerR = CHILD_DIRECTIONS[i][1] * TWO_THIRDS; - centersQ[index] = centerQ; - centersR[index] = centerR; - double distance = hexDistance(localQ, localR, centerQ, centerR) / ONE_THIRD; - - if (distance <= 1D && distance < bestDistance) { - bestDistance = distance; - bestIndex = index; + if (!shouldContinue(childIndex, nodeHash, level - 1)) { + break; } } - return bestIndex; + double centerX = SQRT_3 * (centerQ + (centerR * 0.5D)); + double centerZ = 1.5D * centerR; + double simplexValue = heat.noise(centerX * HEAT_SCALE, y * HEAT_SCALE, centerZ * HEAT_SCALE); + return (simplexValue * 0.82D) + (random01(nodeHash, 31 + level) * 0.18D); } - private boolean shouldContinue(int level, int childIndex, long nodeHash) { + private boolean shouldContinue(int childIndex, long nodeHash, int level) { if (level >= MAX_DEPTH - 1) { return false; } - double gate; - - if (childIndex == 0) { - gate = CENTER_CONTINUE; - } else { - int rotation = rotationForNode(nodeHash); - int parity = parityForNode(nodeHash); - int ringIndex = ringIndexForDirection(childIndex - 1, rotation); - boolean large = (ringIndex % 2) == parity; - gate = large ? LARGE_CONTINUE : SMALL_CONTINUE; - } - - double randomValue = random01(nodeHash, 97 + level); - return randomValue <= gate; - } - - 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); - double centerQ = rounded[0]; - double centerR = rounded[1]; - double radius = 0.5D; - double localQ = (qWorld - centerQ) / radius; - double localR = (rWorld - centerR) / radius; - long nodeHash = mix(seed ^ (rounded[0] * CONST_X) ^ (rounded[1] * CONST_Z)); - double heat = cellHeat(centerQ, centerR, nodeHash, 0); - - for (int level = 0; level < MAX_DEPTH; level++) { - int childIndex = pickChildIndex(localQ, localR, centersQ, centersR); - - if (childIndex < 0) { - return heat; - } - - double childCenterQ = centersQ[childIndex]; - double childCenterR = centersR[childIndex]; - centerQ += childCenterQ * radius; - centerR += childCenterR * radius; - radius *= ONE_THIRD; - localQ = (localQ - childCenterQ) / ONE_THIRD; - localR = (localR - childCenterR) / ONE_THIRD; - nodeHash = nextNodeHash(nodeHash, childIndex, level); - heat = cellHeat(centerQ, centerR, nodeHash, level + 1); - - if (!shouldContinue(level, childIndex, nodeHash)) { - return heat; + double gate = CENTER_CONTINUE; + if (childIndex != 0) { + int rotation = (int) (random01(nodeHash, 3) * 6D); + int parity = random01(nodeHash, 5) >= 0.5D ? 1 : 0; + int direction = childIndex - 1 - rotation; + if (direction < 0) { + direction += 6; } + gate = (direction % 2) == parity ? LARGE_CONTINUE : SMALL_CONTINUE; } - return heat; + return random01(nodeHash, 97 + level) <= gate; } - @Override - public double noise(double x) { - return sample(x, 0D); + private double random01(long nodeHash, int salt) { + long mixed = nodeHash ^ (CONST_X * (salt + 1L)) ^ (CONST_Z * (salt + 7L)); + return (mix(mixed) >>> 11) * 0x1.0p-53; } - @Override - public double noise(double x, double z) { - return sample(x, z); - } - - @Override - public double noise(double x, double y, double z) { - if (z == 0D) { - return sample(x, y); - } - - double a = sample(x, z); - double b = sample(y, x); - double c = sample(z, y); - return (a + b + c) / 3D; - } - - @Override - 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 9b034946f..abd5a646a 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 @@ -2,259 +2,77 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.math.RNG; -public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise { - private static final double SQRT_3_OVER_3 = Math.sqrt(3.0) / 3.0; - private static final double TWO_OVER_THREE = 2.0 / 3.0; - private static final double SQRT_3 = Math.sqrt(3.0); - private static final double THREE_OVER_TWO = 1.5D; - private static final double ONE_THIRD = 1D / 3D; - private static final double TWO_THIRDS = 2D / 3D; +public class HexRandomSizeNoise extends HexagonalNoise implements OctaveNoise { private static final int MAX_DEPTH = 8; - private static final double HEAT_SCALE = 0.24D; - private static final double STRUCTURE_SCALE = 0.36D; + private static final double HEAT_SCALE = 18D; + private static final double STRUCTURE_SCALE = 36D; private static final double SUBDIVIDE_BASE_THRESHOLD = 0.22D; private static final double SUBDIVIDE_LEVEL_STEP = 0.11D; - private static final long CONST_X = 0x9E3779B97F4A7C15L; - private static final long CONST_Z = 0xC2B2AE3D27D4EB4FL; - private static final long[][] CHILD_DIRECTIONS = new long[][]{ - {1L, 0L}, - {1L, -1L}, - {0L, -1L}, - {-1L, 0L}, - {-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; - private int octaves; + private final SimplexNoise heat; + private final SimplexNoise structure; public HexRandomSizeNoise(long seed) { RNG rng = new RNG(seed); this.seed = rng.lmax(); - this.heatSimplex = new SimplexNoise(rng.nextParallelRNG(221L).lmax()); - this.structureSimplex = new SimplexNoise(rng.nextParallelRNG(442L).lmax()); - this.octaves = 1; + this.heat = new SimplexNoise(rng.nextParallelRNG(221L).lmax()); + this.structure = new SimplexNoise(rng.nextParallelRNG(442L).lmax()); } - private double clip(double value) { - if (value < 0D) { - return 0D; + @Override + public void setOctaves(int octaves) { + heat.setOctaves(octaves); + structure.setOctaves(octaves); + } + + @Override + protected double sample(double x, double y, double z) { + double qWorld = (SQRT_3_OVER_3 * x) - (z / 3D); + double rWorld = TWO_OVER_THREE * z; + long rootQ = roundQ(qWorld, rWorld); + long rootR = roundR(qWorld, rWorld); + double centerQ = rootQ; + double centerR = rootR; + double radius = 1D; + double localQ = qWorld - centerQ; + double localR = rWorld - centerR; + long nodeHash = mix(seed ^ (rootQ * CONST_X) ^ (rootR * CONST_Z)); + int level = 0; + + while (level < MAX_DEPTH - 1 && shouldSubdivide(centerQ, centerR, nodeHash, level)) { + int childIndex = pickChildIndex(localQ, localR); + if (childIndex < 0) { + break; + } + + double childQ = CHILD_Q[childIndex]; + double childR = CHILD_R[childIndex]; + centerQ += childQ * radius; + centerR += childR * radius; + radius *= ONE_THIRD; + localQ = (localQ - childQ) * 3D; + localR = (localR - childR) * 3D; + nodeHash = mix(nodeHash ^ (CONST_Z * (childIndex + 1L)) ^ (CONST_X * (level + 1L))); + level++; } - if (value > 1D) { - return 1D; - } - - return value; + double centerX = SQRT_3 * (centerQ + (centerR * 0.5D)); + double centerZ = 1.5D * centerR; + double simplexValue = heat.noise(centerX * HEAT_SCALE, y * HEAT_SCALE, centerZ * HEAT_SCALE); + return (simplexValue * 0.74D) + (random01(nodeHash, 11 + level) * 0.26D); } - private long mix(long input) { - input = (input ^ (input >>> 33)) * 0xff51afd7ed558ccdL; - input = (input ^ (input >>> 33)) * 0xc4ceb9fe1a85ec53L; - return input ^ (input >>> 33); - } - - private double hashToUnit(long value) { - long normalized = mix(value) & Long.MAX_VALUE; - return normalized / (double) Long.MAX_VALUE; + private boolean shouldSubdivide(double centerQ, double centerR, long nodeHash, int level) { + double centerX = SQRT_3 * (centerQ + (centerR * 0.5D)); + double centerZ = 1.5D * centerR; + double simplexValue = structure.noise(centerX * STRUCTURE_SCALE, centerZ * STRUCTURE_SCALE); + double score = (simplexValue * 0.68D) + (random01(nodeHash, 71 + level) * 0.32D); + return score > SUBDIVIDE_BASE_THRESHOLD + (level * SUBDIVIDE_LEVEL_STEP); } private double random01(long nodeHash, int salt) { long mixed = nodeHash ^ (CONST_X * (salt + 1L)) ^ (CONST_Z * (salt + 31L)); - return hashToUnit(mixed); + return (mix(mixed) >>> 11) * 0x1.0p-53; } - private long nextNodeHash(long nodeHash, int childIndex, int level) { - long mixed = nodeHash ^ (CONST_Z * (childIndex + 1L)) ^ (CONST_X * (level + 1L)); - return mix(mixed); - } - - private long[] roundAxial(double q, double r) { - double cubeX = q; - double cubeZ = r; - double cubeY = -cubeX - cubeZ; - long roundedX = Math.round(cubeX); - long roundedY = Math.round(cubeY); - long roundedZ = Math.round(cubeZ); - double xDiff = Math.abs(roundedX - cubeX); - double yDiff = Math.abs(roundedY - cubeY); - double zDiff = Math.abs(roundedZ - cubeZ); - - if (xDiff > yDiff && xDiff > zDiff) { - roundedX = -roundedY - roundedZ; - } else if (yDiff > zDiff) { - roundedY = -roundedX - roundedZ; - } else { - roundedZ = -roundedX - roundedY; - } - - return new long[]{roundedX, roundedZ}; - } - - private double hexDistance(double q, double r, double centerQ, double centerR) { - double deltaQ = q - centerQ; - double deltaR = r - centerR; - double deltaY = -deltaQ - deltaR; - return Math.max(Math.abs(deltaQ), Math.max(Math.abs(deltaR), Math.abs(deltaY))); - } - - private double axialX(double q, double r) { - return SQRT_3 * (q + (r * 0.5D)); - } - - private double axialZ(double r) { - return THREE_OVER_TWO * r; - } - - private double simplexField(SimplexNoise simplex, double x, double z, double scale) { - if (octaves <= 1) { - return simplex.noise(x * scale, z * scale); - } - - double frequency = 1D; - double amplitude = 1D; - double total = 0D; - double max = 0D; - - for (int i = 0; i < octaves; i++) { - total += simplex.noise((x * scale) * frequency, (z * scale) * frequency) * amplitude; - max += amplitude; - frequency *= 2D; - amplitude *= 0.5D; - } - - if (max == 0D) { - return 0.5D; - } - - return clip(total / max); - } - - private double cellHeat(double centerQ, double centerR, long nodeHash, int level) { - double x = axialX(centerQ, centerR); - double z = axialZ(centerR); - double scale = HEAT_SCALE * (1D + (level * 0.2D)); - double simplexValue = simplexField(heatSimplex, x, z, scale); - double randomValue = random01(nodeHash, 11 + level); - return clip((simplexValue * 0.74D) + (randomValue * 0.26D)); - } - - private boolean shouldSubdivide(double centerQ, double centerR, long nodeHash, int level) { - if (level >= MAX_DEPTH - 1) { - return false; - } - - double x = axialX(centerQ, centerR); - double z = axialZ(centerR); - double scale = STRUCTURE_SCALE * (1D + (level * 0.31D)); - double simplexValue = simplexField(structureSimplex, x, z, scale); - double randomValue = random01(nodeHash, 71 + level); - double score = clip((simplexValue * 0.68D) + (randomValue * 0.32D)); - double threshold = SUBDIVIDE_BASE_THRESHOLD + (level * SUBDIVIDE_LEVEL_STEP); - return score > threshold; - } - - private int pickChildIndex(double localQ, double localR, double[] centersQ, double[] centersR) { - int bestIndex = -1; - double bestDistance = Double.POSITIVE_INFINITY; - - centersQ[0] = 0D; - centersR[0] = 0D; - double centerDistance = hexDistance(localQ, localR, 0D, 0D) / ONE_THIRD; - - if (centerDistance <= 1D && centerDistance < bestDistance) { - bestDistance = centerDistance; - bestIndex = 0; - } - - for (int i = 0; i < 6; i++) { - int index = i + 1; - double centerQ = CHILD_DIRECTIONS[i][0] * TWO_THIRDS; - double centerR = CHILD_DIRECTIONS[i][1] * TWO_THIRDS; - centersQ[index] = centerQ; - centersR[index] = centerR; - double distance = hexDistance(localQ, localR, centerQ, centerR) / ONE_THIRD; - - if (distance <= 1D && distance < bestDistance) { - bestDistance = distance; - bestIndex = index; - } - } - - return bestIndex; - } - - 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); - double centerQ = rounded[0]; - double centerR = rounded[1]; - double radius = 0.5D; - double localQ = (qWorld - centerQ) / radius; - double localR = (rWorld - centerR) / radius; - long nodeHash = mix(seed ^ (rounded[0] * CONST_X) ^ (rounded[1] * CONST_Z)); - double heat = cellHeat(centerQ, centerR, nodeHash, 0); - - for (int level = 0; level < MAX_DEPTH; level++) { - if (!shouldSubdivide(centerQ, centerR, nodeHash, level)) { - return heat; - } - - int childIndex = pickChildIndex(localQ, localR, centersQ, centersR); - - if (childIndex < 0) { - return heat; - } - - double childCenterQ = centersQ[childIndex]; - double childCenterR = centersR[childIndex]; - centerQ += childCenterQ * radius; - centerR += childCenterR * radius; - radius *= ONE_THIRD; - localQ = (localQ - childCenterQ) / ONE_THIRD; - localR = (localR - childCenterR) / ONE_THIRD; - nodeHash = nextNodeHash(nodeHash, childIndex, level); - heat = cellHeat(centerQ, centerR, nodeHash, level + 1); - } - - return heat; - } - - @Override - public double noise(double x) { - return sample(x, 0D); - } - - @Override - public double noise(double x, double z) { - return sample(x, z); - } - - @Override - public double noise(double x, double y, double z) { - if (z == 0D) { - return sample(x, y); - } - - double a = sample(x, z); - double b = sample(y, x); - double c = sample(z, y); - return (a + b + c) / 3D; - } - - @Override - 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/HexSimplexNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/HexSimplexNoise.java index 554896d16..b7a9a113f 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/HexSimplexNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexSimplexNoise.java @@ -2,124 +2,27 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.math.RNG; -public class HexSimplexNoise implements NoiseGenerator, OctaveNoise { - private static final double SQRT_3_OVER_3 = Math.sqrt(3.0) / 3.0; - private static final double TWO_OVER_THREE = 2.0 / 3.0; - private static final double SQRT_3 = Math.sqrt(3.0); - private static final double THREE_OVER_TWO = 1.5D; - private static final double CELL_HEAT_SCALE = 2.7D; - private final SimplexNoise cellHeatSimplex; - private int octaves; +public class HexSimplexNoise extends HexagonalNoise implements OctaveNoise { + private static final double HEAT_SCALE = 18D; + private final SimplexNoise heat; public HexSimplexNoise(long seed) { - RNG rng = new RNG(seed); - this.cellHeatSimplex = new SimplexNoise(rng.nextParallelRNG(811L).lmax()); - this.octaves = 1; + this.heat = new SimplexNoise(new RNG(seed).nextParallelRNG(811L).lmax()); } - private double clip(double value) { - if (value < 0D) { - return 0D; - } - - if (value > 1D) { - return 1D; - } - - return value; + @Override + public void setOctaves(int octaves) { + heat.setOctaves(octaves); } - private long[] roundAxial(double q, double r) { - double cubeX = q; - double cubeZ = r; - double cubeY = -cubeX - cubeZ; - long roundedX = Math.round(cubeX); - long roundedY = Math.round(cubeY); - long roundedZ = Math.round(cubeZ); - double xDiff = Math.abs(roundedX - cubeX); - double yDiff = Math.abs(roundedY - cubeY); - double zDiff = Math.abs(roundedZ - cubeZ); - - if (xDiff > yDiff && xDiff > zDiff) { - roundedX = -roundedY - roundedZ; - } else if (yDiff > zDiff) { - roundedY = -roundedX - roundedZ; - } else { - roundedZ = -roundedX - roundedY; - } - - return new long[]{roundedX, roundedZ}; - } - - private double axialX(long q, long r) { - return SQRT_3 * (q + (r * 0.5D)); - } - - private double axialZ(long r) { - return THREE_OVER_TWO * r; - } - - private double cellHeat(long q, long r, double frequency) { - double x = axialX(q, r); - double z = axialZ(r); - return cellHeatSimplex.noise((x * CELL_HEAT_SCALE) * frequency, (z * CELL_HEAT_SCALE) * frequency); - } - - private double sampleCell(long q, long r) { - if (octaves <= 1) { - return cellHeat(q, r, 1D); - } - - double frequency = 1D; - double amplitude = 1D; - double total = 0D; - double max = 0D; - - for (int i = 0; i < octaves; i++) { - total += cellHeat(q, r, frequency) * amplitude; - max += amplitude; - frequency *= 2D; - amplitude *= 0.5D; - } - - if (max == 0D) { - return 0.5D; - } - - return clip(total / max); - } - - private double sample(double x, double z) { - double q = (SQRT_3_OVER_3 * x) - (z / 3.0); + @Override + protected double sample(double x, double y, double z) { + double q = (SQRT_3_OVER_3 * x) - (z / 3D); double r = TWO_OVER_THREE * z; - long[] rounded = roundAxial(q, r); - return sampleCell(rounded[0], rounded[1]); - } - - @Override - public double noise(double x) { - return sample(x, 0D); - } - - @Override - public double noise(double x, double z) { - return sample(x, z); - } - - @Override - public double noise(double x, double y, double z) { - if (z == 0D) { - return sample(x, y); - } - - double a = sample(x, z); - double b = sample(y, x); - double c = sample(z, y); - return (a + b + c) / 3D; - } - - @Override - public void setOctaves(int o) { - this.octaves = Math.max(1, o); + long centerQ = roundQ(q, r); + long centerR = roundR(q, r); + double centerX = SQRT_3 * (centerQ + (centerR * 0.5D)); + double centerZ = 1.5D * centerR; + return heat.noise(centerX * HEAT_SCALE, y * HEAT_SCALE, centerZ * HEAT_SCALE); } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/HexagonNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/HexagonNoise.java index 0f3181d3d..547a2a8fb 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/HexagonNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexagonNoise.java @@ -2,82 +2,27 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.math.RNG; -public class HexagonNoise implements NoiseGenerator { - private static final double SQRT_3_OVER_3 = Math.sqrt(3.0) / 3.0; - private static final double TWO_OVER_THREE = 2.0 / 3.0; - private static final long CONST_X = 0x9E3779B97F4A7C15L; - private static final long CONST_Z = 0xC2B2AE3D27D4EB4FL; - private final long seed; +public class HexagonNoise extends HexagonalNoise implements OctaveNoise { + private static final double HEAT_SCALE = 18D; + private final SimplexNoise heat; public HexagonNoise(long seed) { - this.seed = new RNG(seed).lmax(); + this.heat = new SimplexNoise(new RNG(seed).lmax()); } - private long mix(long input) { - input = (input ^ (input >>> 33)) * 0xff51afd7ed558ccdL; - input = (input ^ (input >>> 33)) * 0xc4ceb9fe1a85ec53L; - return input ^ (input >>> 33); + @Override + public void setOctaves(int octaves) { + heat.setOctaves(octaves); } - private long hash(long q, long r) { - long hash = seed; - hash ^= mix(q + CONST_X); - hash ^= mix(r + CONST_Z); - return mix(hash); - } - - private long[] roundAxial(double q, double r) { - double cubeX = q; - double cubeZ = r; - double cubeY = -cubeX - cubeZ; - - long rx = Math.round(cubeX); - long ry = Math.round(cubeY); - long rz = Math.round(cubeZ); - - double xDiff = Math.abs(rx - cubeX); - double yDiff = Math.abs(ry - cubeY); - double zDiff = Math.abs(rz - cubeZ); - - if (xDiff > yDiff && xDiff > zDiff) { - rx = -ry - rz; - } else if (yDiff > zDiff) { - ry = -rx - rz; - } else { - rz = -rx - ry; - } - - return new long[]{rx, rz}; - } - - private double sampleCell(double x, double z) { - double q = (SQRT_3_OVER_3 * x) - (z / 3.0); + @Override + protected double sample(double x, double y, double z) { + double q = (SQRT_3_OVER_3 * x) - (z / 3D); double r = TWO_OVER_THREE * z; - long[] axial = roundAxial(q, r); - long hashed = hash(axial[0], axial[1]); - long normalized = hashed & Long.MAX_VALUE; - return normalized / (double) Long.MAX_VALUE; - } - - @Override - public double noise(double x) { - return sampleCell(x, 0D); - } - - @Override - public double noise(double x, double z) { - return sampleCell(x, z); - } - - @Override - public double noise(double x, double y, double z) { - if (z == 0D) { - return sampleCell(x, y); - } - - double a = sampleCell(x, z); - double b = sampleCell(y, x); - double c = sampleCell(z, y); - return (a + b + c) / 3D; + long centerQ = roundQ(q, r); + long centerR = roundR(q, r); + double centerX = SQRT_3 * (centerQ + (centerR * 0.5D)); + double centerZ = 1.5D * centerR; + return heat.noise(centerX * HEAT_SCALE, y * HEAT_SCALE, centerZ * HEAT_SCALE); } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/HexagonalNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/HexagonalNoise.java new file mode 100644 index 000000000..d27dde76f --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexagonalNoise.java @@ -0,0 +1,71 @@ +package art.arcane.iris.util.project.noise; + +abstract class HexagonalNoise implements NoiseGenerator { + protected static final double SQRT_3 = Math.sqrt(3D); + protected static final double SQRT_3_OVER_3 = SQRT_3 / 3D; + protected static final double TWO_OVER_THREE = 2D / 3D; + protected static final double ONE_THIRD = 1D / 3D; + protected static final double[] CHILD_Q = {0D, 4D / 9D, 2D / 9D, -2D / 9D, -4D / 9D, -2D / 9D, 2D / 9D}; + protected static final double[] CHILD_R = {0D, -2D / 9D, 2D / 9D, 4D / 9D, 2D / 9D, -2D / 9D, -4D / 9D}; + protected static final long CONST_X = 0x9E3779B97F4A7C15L; + protected static final long CONST_Z = 0xC2B2AE3D27D4EB4FL; + + @Override + public double noise(double x) { + return sample(x, 0D, 0D); + } + + @Override + public double noise(double x, double z) { + return sample(x, 0D, z); + } + + @Override + public double noise(double x, double y, double z) { + return sample(x, y, z); + } + + protected abstract double sample(double x, double y, double z); + + protected static long roundQ(double q, double r) { + double cubeY = -q - r; + long roundedQ = Math.round(q); + long roundedR = Math.round(r); + long roundedY = Math.round(cubeY); + double qDiff = Math.abs(roundedQ - q); + double rDiff = Math.abs(roundedR - r); + double yDiff = Math.abs(roundedY - cubeY); + return qDiff > yDiff && qDiff > rDiff ? -roundedY - roundedR : roundedQ; + } + + protected static long roundR(double q, double r) { + double cubeY = -q - r; + long roundedQ = Math.round(q); + long roundedR = Math.round(r); + long roundedY = Math.round(cubeY); + double qDiff = Math.abs(roundedQ - q); + double rDiff = Math.abs(roundedR - r); + double yDiff = Math.abs(roundedY - cubeY); + return !(qDiff > yDiff && qDiff > rDiff) && yDiff <= rDiff ? -roundedQ - roundedY : roundedR; + } + + protected static long mix(long input) { + input = (input ^ (input >>> 33)) * 0xff51afd7ed558ccdL; + input = (input ^ (input >>> 33)) * 0xc4ceb9fe1a85ec53L; + return input ^ (input >>> 33); + } + + protected static int pickChildIndex(double localQ, double localR) { + for (int index = 0; index < CHILD_Q.length; index++) { + double deltaQ = localQ - CHILD_Q[index]; + double deltaR = localR - CHILD_R[index]; + double distance = Math.max(Math.abs((2D * deltaQ) + deltaR), + Math.max(Math.abs(deltaQ + (2D * deltaR)), Math.abs(deltaQ - deltaR))); + if (distance <= ONE_THIRD) { + return index; + } + } + + return -1; + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/InterpolatedNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/InterpolatedNoise.java index 0991b458a..40ef42a23 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/InterpolatedNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/InterpolatedNoise.java @@ -21,14 +21,16 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.function.NoiseProvider; import art.arcane.iris.util.project.interpolation.InterpolationMethod; import art.arcane.iris.util.project.interpolation.IrisInterpolation; -public class InterpolatedNoise implements NoiseGenerator { +public class InterpolatedNoise implements NoiseGenerator, OctaveNoise { private final InterpolationMethod method; + private final NoiseGenerator generator; private final NoiseProvider p; public InterpolatedNoise(long seed, NoiseType type, InterpolationMethod method) { this.method = method; - NoiseGenerator g = type.create(seed); - p = g::noise; + generator = type.create(seed); + double coordinateScale = type.getCoordinateScale(); + p = (x, z) -> generator.noise(x * coordinateScale, z * coordinateScale); } @Override @@ -38,15 +40,18 @@ public class InterpolatedNoise implements NoiseGenerator { @Override public double noise(double x, double z) { - return IrisInterpolation.getNoise(method, (int) x, (int) z, 32, p); + return Math.max(0D, Math.min(1D, IrisInterpolation.getNoise(method, x, z, 32, p))); } @Override public double noise(double x, double y, double z) { - if (z == 0) { - return noise(x, y); - } + return noise(x, z); + } - return IrisInterpolation.getNoise(method, (int) x, (int) z, 32, p); + @Override + public void setOctaves(int octaves) { + if (generator instanceof OctaveNoise octaveNoise) { + octaveNoise.setOctaves(octaves); + } } } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/NoiseType.java b/core/src/main/java/art/arcane/iris/util/project/noise/NoiseType.java index 1d0cc2a78..0dbd95513 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/NoiseType.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/NoiseType.java @@ -20,79 +20,89 @@ package art.arcane.iris.util.project.noise; import art.arcane.iris.util.project.interpolation.InterpolationMethod; public enum NoiseType { - WHITE(WhiteNoise::new), - WHITE_BILINEAR((s) -> new InterpolatedNoise(s, WHITE, InterpolationMethod.BILINEAR)), - WHITE_BICUBIC((s) -> new InterpolatedNoise(s, WHITE, InterpolationMethod.BICUBIC)), - WHITE_HERMITE((s) -> new InterpolatedNoise(s, WHITE, InterpolationMethod.HERMITE)), + WHITE(WhiteNoise::new, 1D), + WHITE_BILINEAR((s) -> new InterpolatedNoise(s, WHITE, InterpolationMethod.BILINEAR), 1D), + WHITE_BICUBIC((s) -> new InterpolatedNoise(s, WHITE, InterpolationMethod.BICUBIC), 1D), + WHITE_HERMITE((s) -> new InterpolatedNoise(s, WHITE, InterpolationMethod.HERMITE), 1D), SIMPLEX(SimplexNoise::new), - PERLIN(seed -> new PerlinNoise(seed).hermite()), + PERLIN(PerlinNoise::new), FRACTAL_BILLOW_SIMPLEX(FractalBillowSimplexNoise::new), FRACTAL_BILLOW_PERLIN(FractalBillowPerlinNoise::new), FRACTAL_FBM_SIMPLEX(FractalFBMSimplexNoise::new), FRACTAL_RIGID_MULTI_SIMPLEX(FractalRigidMultiSimplexNoise::new), - FLAT(FlatNoise::new), + FLAT(FlatNoise::new, 1D), CELLULAR(CellularNoise::new), - CELLULAR_BILINEAR((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR)), - CELLULAR_BILINEAR_STARCAST_3((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_3)), - CELLULAR_BILINEAR_STARCAST_6((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_6)), - CELLULAR_BILINEAR_STARCAST_9((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_9)), - CELLULAR_BILINEAR_STARCAST_12((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_12)), - CELLULAR_BICUBIC((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BICUBIC)), - CELLULAR_HERMITE((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE)), - CELLULAR_STARCAST_3((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_3)), - CELLULAR_STARCAST_6((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_6)), - CELLULAR_STARCAST_9((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_9)), - CELLULAR_STARCAST_12((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_12)), - CELLULAR_HERMITE_STARCAST_3((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_3)), - CELLULAR_HERMITE_STARCAST_6((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_6)), - CELLULAR_HERMITE_STARCAST_9((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_9)), - CELLULAR_HERMITE_STARCAST_12((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_12)), + CELLULAR_BILINEAR((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR), 1D), + CELLULAR_BILINEAR_STARCAST_3((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_3), 1D), + CELLULAR_BILINEAR_STARCAST_6((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_6), 1D), + CELLULAR_BILINEAR_STARCAST_9((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_9), 1D), + CELLULAR_BILINEAR_STARCAST_12((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BILINEAR_STARCAST_12), 1D), + CELLULAR_BICUBIC((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.BICUBIC), 1D), + CELLULAR_HERMITE((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE), 1D), + CELLULAR_STARCAST_3((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_3), 1D), + CELLULAR_STARCAST_6((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_6), 1D), + CELLULAR_STARCAST_9((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_9), 1D), + CELLULAR_STARCAST_12((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.STARCAST_12), 1D), + CELLULAR_HERMITE_STARCAST_3((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_3), 1D), + CELLULAR_HERMITE_STARCAST_6((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_6), 1D), + CELLULAR_HERMITE_STARCAST_9((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_9), 1D), + CELLULAR_HERMITE_STARCAST_12((s) -> new InterpolatedNoise(s, CELLULAR, InterpolationMethod.HERMITE_STARCAST_12), 1D), GLOB(GlobNoise::new), CUBIC(CubicNoise::new), FRACTAL_CUBIC(FractalCubicNoise::new), CELLULAR_HEIGHT(CellHeightNoise::new), - CLOVER(CloverNoise::new), - CLOVER_BILINEAR((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR)), - CLOVER_BILINEAR_STARCAST_3((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_3)), - CLOVER_BILINEAR_STARCAST_6((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_6)), - CLOVER_BILINEAR_STARCAST_9((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_9)), - CLOVER_BILINEAR_STARCAST_12((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_12)), - CLOVER_BICUBIC((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BICUBIC)), - CLOVER_HERMITE((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE)), - CLOVER_STARCAST_3((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_3)), - CLOVER_STARCAST_6((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_6)), - CLOVER_STARCAST_9((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_9)), - CLOVER_STARCAST_12((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_12)), - CLOVER_HERMITE_STARCAST_3((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_3)), - CLOVER_HERMITE_STARCAST_6((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_6)), - CLOVER_HERMITE_STARCAST_9((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_9)), - CLOVER_HERMITE_STARCAST_12((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_12)), - HEXAGON(HexagonNoise::new), - HEXAGON_BILINEAR((s) -> new InterpolatedNoise(s, HEXAGON, InterpolationMethod.BILINEAR)), - HEXAGON_BICUBIC((s) -> new InterpolatedNoise(s, HEXAGON, InterpolationMethod.BICUBIC)), - HEXAGON_HERMITE((s) -> new InterpolatedNoise(s, HEXAGON, InterpolationMethod.HERMITE)), - HEX_JAMES(HexJamesNoise::new), - HEX_JAMES_BILINEAR((s) -> new InterpolatedNoise(s, HEX_JAMES, InterpolationMethod.BILINEAR)), - HEX_JAMES_BICUBIC((s) -> new InterpolatedNoise(s, HEX_JAMES, InterpolationMethod.BICUBIC)), - HEX_JAMES_HERMITE((s) -> new InterpolatedNoise(s, HEX_JAMES, InterpolationMethod.HERMITE)), - HEX_SIMPLEX(HexSimplexNoise::new), - HEX_SIMPLEX_BILINEAR((s) -> new InterpolatedNoise(s, HEX_SIMPLEX, InterpolationMethod.BILINEAR)), - HEX_SIMPLEX_BICUBIC((s) -> new InterpolatedNoise(s, HEX_SIMPLEX, InterpolationMethod.BICUBIC)), - HEX_SIMPLEX_HERMITE((s) -> new InterpolatedNoise(s, HEX_SIMPLEX, InterpolationMethod.HERMITE)), - HEX_RANDOM_SIZE(HexRandomSizeNoise::new), - HEX_RANDOM_SIZE_BILINEAR((s) -> new InterpolatedNoise(s, HEX_RANDOM_SIZE, InterpolationMethod.BILINEAR)), - HEX_RANDOM_SIZE_BICUBIC((s) -> new InterpolatedNoise(s, HEX_RANDOM_SIZE, InterpolationMethod.BICUBIC)), - HEX_RANDOM_SIZE_HERMITE((s) -> new InterpolatedNoise(s, HEX_RANDOM_SIZE, InterpolationMethod.HERMITE)), - SIERPINSKI_TRIANGLE(SierpinskiTriangleNoise::new), - SIERPINSKI_TRIANGLE_BILINEAR((s) -> new InterpolatedNoise(s, SIERPINSKI_TRIANGLE, InterpolationMethod.BILINEAR)), - SIERPINSKI_TRIANGLE_BICUBIC((s) -> new InterpolatedNoise(s, SIERPINSKI_TRIANGLE, InterpolationMethod.BICUBIC)), - SIERPINSKI_TRIANGLE_HERMITE((s) -> new InterpolatedNoise(s, SIERPINSKI_TRIANGLE, InterpolationMethod.HERMITE)), + CLOVER(CloverNoise::new, 1D / 64D), + CLOVER_BILINEAR((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR), 1D), + CLOVER_BILINEAR_STARCAST_3((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_3), 1D), + CLOVER_BILINEAR_STARCAST_6((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_6), 1D), + CLOVER_BILINEAR_STARCAST_9((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_9), 1D), + CLOVER_BILINEAR_STARCAST_12((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BILINEAR_STARCAST_12), 1D), + CLOVER_BICUBIC((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.BICUBIC), 1D), + CLOVER_HERMITE((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE), 1D), + CLOVER_STARCAST_3((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_3), 1D), + CLOVER_STARCAST_6((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_6), 1D), + CLOVER_STARCAST_9((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_9), 1D), + CLOVER_STARCAST_12((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.STARCAST_12), 1D), + CLOVER_HERMITE_STARCAST_3((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_3), 1D), + CLOVER_HERMITE_STARCAST_6((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_6), 1D), + CLOVER_HERMITE_STARCAST_9((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_9), 1D), + CLOVER_HERMITE_STARCAST_12((s) -> new InterpolatedNoise(s, CLOVER, InterpolationMethod.HERMITE_STARCAST_12), 1D), + HEXAGON(HexagonNoise::new, 2D / 64D), + HEXAGON_BILINEAR((s) -> new InterpolatedNoise(s, HEXAGON, InterpolationMethod.BILINEAR), 1D), + HEXAGON_BICUBIC((s) -> new InterpolatedNoise(s, HEXAGON, InterpolationMethod.BICUBIC), 1D), + HEXAGON_HERMITE((s) -> new InterpolatedNoise(s, HEXAGON, InterpolationMethod.HERMITE), 1D), + HEX_JAMES(HexJamesNoise::new, 2D / 64D), + HEX_JAMES_BILINEAR((s) -> new InterpolatedNoise(s, HEX_JAMES, InterpolationMethod.BILINEAR), 1D), + HEX_JAMES_BICUBIC((s) -> new InterpolatedNoise(s, HEX_JAMES, InterpolationMethod.BICUBIC), 1D), + HEX_JAMES_HERMITE((s) -> new InterpolatedNoise(s, HEX_JAMES, InterpolationMethod.HERMITE), 1D), + HEX_SIMPLEX(HexSimplexNoise::new, 2D / 64D), + HEX_SIMPLEX_BILINEAR((s) -> new InterpolatedNoise(s, HEX_SIMPLEX, InterpolationMethod.BILINEAR), 1D), + HEX_SIMPLEX_BICUBIC((s) -> new InterpolatedNoise(s, HEX_SIMPLEX, InterpolationMethod.BICUBIC), 1D), + HEX_SIMPLEX_HERMITE((s) -> new InterpolatedNoise(s, HEX_SIMPLEX, InterpolationMethod.HERMITE), 1D), + HEX_RANDOM_SIZE(HexRandomSizeNoise::new, 2D / 64D), + HEX_RANDOM_SIZE_BILINEAR((s) -> new InterpolatedNoise(s, HEX_RANDOM_SIZE, InterpolationMethod.BILINEAR), 1D), + HEX_RANDOM_SIZE_BICUBIC((s) -> new InterpolatedNoise(s, HEX_RANDOM_SIZE, InterpolationMethod.BICUBIC), 1D), + HEX_RANDOM_SIZE_HERMITE((s) -> new InterpolatedNoise(s, HEX_RANDOM_SIZE, InterpolationMethod.HERMITE), 1D), + SIERPINSKI_TRIANGLE(SierpinskiTriangleNoise::new, 8D / 64D), + SIERPINSKI_TRIANGLE_BILINEAR((s) -> new InterpolatedNoise(s, SIERPINSKI_TRIANGLE, InterpolationMethod.BILINEAR), 1D), + SIERPINSKI_TRIANGLE_BICUBIC((s) -> new InterpolatedNoise(s, SIERPINSKI_TRIANGLE, InterpolationMethod.BICUBIC), 1D), + SIERPINSKI_TRIANGLE_HERMITE((s) -> new InterpolatedNoise(s, SIERPINSKI_TRIANGLE, InterpolationMethod.HERMITE), 1D), VASCULAR(VascularNoise::new); private final NoiseFactory f; + private final double coordinateScale; NoiseType(NoiseFactory f) { + this(f, 100D / 64D); + } + + NoiseType(NoiseFactory f, double coordinateScale) { this.f = f; + this.coordinateScale = coordinateScale; + } + + public double getCoordinateScale() { + return coordinateScale; } public NoiseGenerator create(long seed) { 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 fb3185172..52914e213 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 @@ -5,7 +5,7 @@ import art.arcane.volmlib.util.math.RNG; import org.jetbrains.annotations.NotNull; -public class OffsetNoiseGenerator implements NoiseGenerator { +public class OffsetNoiseGenerator implements NoiseGenerator, OctaveNoise { private static final int GENERIC = 0; private static final int SIMPLEX = 1; private static final int PERLIN = 2; @@ -102,6 +102,13 @@ public class OffsetNoiseGenerator implements NoiseGenerator { }; } + @Override + public void setOctaves(int octaves) { + if (base instanceof OctaveNoise octaveNoise) { + octaveNoise.setOctaves(octaves); + } + } + @Override public boolean isNoScale() { return base.isNoScale(); 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 6cd80cefa..2718e901c 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 @@ -23,9 +23,11 @@ import art.arcane.volmlib.util.math.RNG; public class PerlinNoise implements NoiseGenerator, OctaveNoise { private final FastNoiseDouble n; private int octaves; + private double octaveBounding = 1D; public PerlinNoise(long seed) { this.n = new FastNoiseDouble(new RNG(seed).lmax()); + n.setLongerp(FastNoiseDouble.Longerp.Qulongic); octaves = 1; } @@ -35,128 +37,74 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise { @Override public double noise(double x) { - if (octaves <= 1) { - return f(n.GetPerlin(x, 0)); - } - - double f = 1; - double m = 0; - double v = 0; - - for (int i = 0; i < octaves; i++) { - v += n.GetPerlin((x * (f == 1 ? f++ : (f *= 2))), 0) * f; - m += f; - } - - return f(v / m); + return f(noiseSigned(x)); } @Override public double noiseSigned(double x) { - if (octaves <= 1) { + if (octaves == 1) { return n.GetPerlin(x, 0D); } double frequency = 1D; - double magnitude = 0D; + double amplitude = 1D; 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; + value += n.GetPerlin(x * frequency, 0D) * amplitude; + frequency *= 2D; + amplitude *= 0.5D; } - - return value / magnitude; + return value * octaveBounding; } @Override public double noise(double x, double z) { - if (octaves <= 1) { - return f(n.GetPerlin(x, z)); - } - double f = 1; - double m = 0; - double v = 0; - - for (int i = 0; i < octaves; i++) { - f = f == 1 ? f + 1 : f * 2; - v += n.GetPerlin((x * f), (z * f)) * f; - m += f; - } - - return f(v / m); + return f(noiseSigned(x, z)); } @Override public double noiseSigned(double x, double z) { - if (octaves <= 1) { + if (octaves == 1) { return n.GetPerlin(x, z); } double frequency = 1D; - double magnitude = 0D; + double amplitude = 1D; 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; + value += n.GetPerlin(x * frequency, z * frequency) * amplitude; + frequency *= 2D; + amplitude *= 0.5D; } - - return value / magnitude; + return value * octaveBounding; } @Override public double noise(double x, double y, double z) { - if (octaves <= 1) { - return f(n.GetPerlin(x, y, z)); - } - double f = 1; - double m = 0; - double v = 0; - - for (int i = 0; i < octaves; i++) { - f = f == 1 ? f + 1 : f * 2; - v += n.GetPerlin((x * f), (y * f), (z * f)) * f; - m += f; - } - - return f(v / m); + return f(noiseSigned(x, y, z)); } @Override public double noiseSigned(double x, double y, double z) { - if (octaves <= 1) { + if (octaves == 1) { return n.GetPerlin(x, y, z); } double frequency = 1D; - double magnitude = 0D; + double amplitude = 1D; 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; + value += n.GetPerlin(x * frequency, y * frequency, z * frequency) * amplitude; + frequency *= 2D; + amplitude *= 0.5D; } - - return value / magnitude; + return value * octaveBounding; } @Override - public void setOctaves(int o) { - octaves = o; + public void setOctaves(int octaves) { + this.octaves = Math.max(1, Math.min(16, octaves)); + octaveBounding = 1D / (2D - Math.scalb(1D, 1 - this.octaves)); } public NoiseGenerator hermite() { diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/SierpinskiTriangleNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/SierpinskiTriangleNoise.java index fbf0fa6f6..9eb7eb1ad 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/SierpinskiTriangleNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/SierpinskiTriangleNoise.java @@ -3,96 +3,67 @@ package art.arcane.iris.util.project.noise; import art.arcane.volmlib.util.math.RNG; public class SierpinskiTriangleNoise implements NoiseGenerator, OctaveNoise { - private static final double TRI_SCALE = 0.18D; - private static final double TRI_PROJECTION = 0.8660254037844386D; - private static final double HEAT_SCALE = 0.33D; - private final SimplexNoise heatSimplex; - private int octaves; + private static final double TRIANGLE_SIDE = 8D; + private static final double INVERSE_HEIGHT = 2D / (Math.sqrt(3D) * TRIANGLE_SIDE); + private static final double HEAT_SCALE = 6D; + private static final int DEPTH = 4; + private final SimplexNoise heat; public SierpinskiTriangleNoise(long seed) { - RNG rng = new RNG(seed); - this.heatSimplex = new SimplexNoise(rng.nextParallelRNG(177L).lmax()); - this.octaves = 1; - } - - private double clip(double value) { - if (value < 0D) { - return 0D; - } - - if (value > 1D) { - return 1D; - } - - return value; - } - - private double heat(double x, double z) { - if (octaves <= 1) { - return heatSimplex.noise(x * HEAT_SCALE, z * HEAT_SCALE); - } - - double frequency = 1D; - double amplitude = 1D; - double total = 0D; - double max = 0D; - - for (int i = 0; i < octaves; i++) { - total += heatSimplex.noise((x * HEAT_SCALE) * frequency, (z * HEAT_SCALE) * frequency) * amplitude; - max += amplitude; - frequency *= 2D; - amplitude *= 0.5D; - } - - if (max == 0D) { - return 0.5D; - } - - return clip(total / max); - } - - private double mask(double x, double z) { - double sx = (x * TRI_SCALE) + ((z * TRI_SCALE) * 0.5D); - double sz = (z * TRI_SCALE) * TRI_PROJECTION; - long ix = (long) Math.floor(Math.abs(sx)); - long iz = (long) Math.floor(Math.abs(sz)); - return ((ix & iz) == 0L) ? 1D : 0D; - } - - private double sample(double x, double z) { - double m = mask(x, z); - double h = heat(x, z); - if (m >= 0.5D) { - return clip((h * 0.65D) + 0.35D); - } - - return clip(h * 0.12D); + this.heat = new SimplexNoise(new RNG(seed).nextParallelRNG(177L).lmax()); } @Override public double noise(double x) { - return sample(x, 0D); + return sample(x, 0D, 0D); } @Override public double noise(double x, double z) { - return sample(x, z); + return sample(x, 0D, z); } @Override public double noise(double x, double y, double z) { - if (z == 0D) { - return sample(x, y); - } - - double a = sample(x, z); - double b = sample(y, x); - double c = sample(z, y); - return (a + b + c) / 3D; + return sample(x, y, z); } @Override - public void setOctaves(int o) { - this.octaves = Math.max(1, o); + public void setOctaves(int octaves) { + heat.setOctaves(octaves); + } + + private boolean contains(double x, double z) { + double v = z * INVERSE_HEIGHT; + double u = (x / TRIANGLE_SIDE) - (v * 0.5D); + u -= Math.floor(u); + v -= Math.floor(v); + + if (u + v > 1D) { + u = 1D - u; + v = 1D - v; + } + + for (int depth = 0; depth < DEPTH; depth++) { + if (u >= 0.5D) { + u = (u * 2D) - 1D; + v *= 2D; + } else if (v >= 0.5D) { + u *= 2D; + v = (v * 2D) - 1D; + } else if (u + v <= 0.5D) { + u *= 2D; + v *= 2D; + } else { + return false; + } + } + + return true; + } + + private double sample(double x, double y, double z) { + double value = heat.noise(x * HEAT_SCALE, y * HEAT_SCALE, z * HEAT_SCALE); + return contains(x, z) ? (value * 0.65D) + 0.35D : value * 0.12D; } } 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 5c65a0fa2..22e7379a5 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 @@ -23,6 +23,7 @@ import art.arcane.volmlib.util.math.RNG; public class SimplexNoise implements NoiseGenerator, OctaveNoise { private final FastNoiseDouble n; private int octaves; + private double octaveBounding = 1D; public SimplexNoise(long seed) { this.n = new FastNoiseDouble(new RNG(seed).lmax()); @@ -35,127 +36,74 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise { @Override public double noise(double x) { - if (octaves <= 1) { - return f(n.GetSimplex(x, 0d)); - } - - double f = 1; - double m = 0; - double v = 0; - - for (int i = 0; i < octaves; i++) { - v += n.GetSimplex((x * (f == 1 ? f++ : (f *= 2))), 0d) * f; - m += f; - } - - return f(v / m); + return f(noiseSigned(x)); } @Override public double noiseSigned(double x) { - if (octaves <= 1) { + if (octaves == 1) { return n.GetSimplex(x, 0D); } double frequency = 1D; - double magnitude = 0D; + double amplitude = 1D; 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; + value += n.GetSimplex(x * frequency, 0D) * amplitude; + frequency *= 2D; + amplitude *= 0.5D; } - - return value / magnitude; + return value * octaveBounding; } @Override public double noise(double x, double z) { - if (octaves <= 1) { - return f(n.GetSimplex(x, z)); - } - double f = 1; - double m = 0; - double v = 0; - - for (int i = 0; i < octaves; i++) { - f = f == 1 ? f + 1 : f * 2; - v += n.GetSimplex((x * f), (z * f)) * f; - m += f; - } - - return f(v / m); + return f(noiseSigned(x, z)); } @Override public double noiseSigned(double x, double z) { - if (octaves <= 1) { + if (octaves == 1) { return n.GetSimplex(x, z); } double frequency = 1D; - double magnitude = 0D; + double amplitude = 1D; 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; + value += n.GetSimplex(x * frequency, z * frequency) * amplitude; + frequency *= 2D; + amplitude *= 0.5D; } - - return value / magnitude; + return value * octaveBounding; } @Override public double noise(double x, double y, double z) { - if (octaves <= 1) { - return f(n.GetSimplex(x, y, z)); - } - double f = 1; - double m = 0; - double v = 0; - - for (int i = 0; i < octaves; i++) { - f = f == 1 ? f + 1 : f * 2; - v += n.GetSimplex((x * f), (y * f), (z * f)) * f; - m += f; - } - - return f(v / m); + return f(noiseSigned(x, y, z)); } @Override public double noiseSigned(double x, double y, double z) { - if (octaves <= 1) { + if (octaves == 1) { return n.GetSimplex(x, y, z); } double frequency = 1D; - double magnitude = 0D; + double amplitude = 1D; 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; + value += n.GetSimplex(x * frequency, y * frequency, z * frequency) * amplitude; + frequency *= 2D; + amplitude *= 0.5D; } - - return value / magnitude; + return value * octaveBounding; } @Override - public void setOctaves(int o) { - octaves = o; + public void setOctaves(int octaves) { + this.octaves = Math.max(1, Math.min(16, octaves)); + octaveBounding = 1D / (2D - Math.scalb(1D, 1 - this.octaves)); } + } 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 f58119c51..8b18d3c88 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 @@ -31,7 +31,7 @@ public class VascularNoise implements NoiseGenerator { } private double filter(double noise) { - double normalized = (noise * 0.5D) + 0.5D; + double normalized = 0.5D - noise * 0.5D; if (normalized < 0D) { return 0D; } @@ -40,6 +40,7 @@ public class VascularNoise implements NoiseGenerator { } private double filterSigned(double noise) { + noise = -noise; if (noise < -1D) { return -1D; } diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/WhiteNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/WhiteNoise.java index f929682a3..717262401 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/WhiteNoise.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/WhiteNoise.java @@ -18,13 +18,11 @@ package art.arcane.iris.util.project.noise; -import art.arcane.volmlib.util.math.RNG; - public class WhiteNoise implements NoiseGenerator { - private final FastNoise n; + private final FastNoiseDouble n; public WhiteNoise(long seed) { - n = new FastNoise(new RNG(seed).imax()); + n = new FastNoiseDouble(seed); } public boolean isStatic() { @@ -35,22 +33,18 @@ public class WhiteNoise implements NoiseGenerator { return true; } - private double f(double m) { - return (m % 8192) * 1024; - } - @Override public double noise(double x) { - return (n.GetWhiteNoise(f(x), 0d) / 2D) + 0.5D; + return (n.GetWhiteNoise(x, 0d) / 2D) + 0.5D; } @Override public double noise(double x, double z) { - return (n.GetWhiteNoise(f(x), f(z)) / 2D) + 0.5D; + return (n.GetWhiteNoise(x, z) / 2D) + 0.5D; } @Override public double noise(double x, double y, double z) { - return (n.GetWhiteNoise(f(x), f(y), f(z)) / 2D) + 0.5D; + return (n.GetWhiteNoise(x, y, z) / 2D) + 0.5D; } } diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3DNearParityTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3DNearParityTest.java index f52221e2b..387e865cd 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3DNearParityTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3DNearParityTest.java @@ -51,6 +51,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; public class IrisCaveCarver3DNearParityTest { + private static final long CARVE_SEED = -54_777_863_784_492_918L; private static final int SURFACE_CEILING_FADE_DEPTH = 12; private static final double SURFACE_CEILING_SOLID_EPSILON = 0.000001D; @@ -297,11 +298,11 @@ public class IrisCaveCarver3DNearParityTest { public void warpCacheDoesNotCrossContaminateCarverInstancesOnSameThread() throws Exception { Engine engine = createEngine(128, 92); IrisCaveProfile firstProfile = createProfile(true, false); - firstProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.12D)); + firstProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.1875D)); IrisCaveProfile secondProfile = createProfile(true, false); - secondProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.12D)); + secondProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.1875D)); IrisCaveProfile controlProfile = createProfile(true, false); - controlProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.12D)); + controlProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.1875D)); IrisCaveCarver3D firstCarver = new IrisCaveCarver3D(engine, firstProfile); IrisCaveCarver3D secondCarver = new IrisCaveCarver3D(engine, secondProfile); IrisCaveCarver3D controlCarver = new IrisCaveCarver3D(engine, controlProfile); @@ -1101,13 +1102,14 @@ public class IrisCaveCarver3DNearParityTest { Engine engine = mock(Engine.class); IrisData data = mock(IrisData.class); IrisDimension dimension = mock(IrisDimension.class); - SeedManager seedManager = new SeedManager(942_337_445L); + SeedManager seedManager = mock(SeedManager.class); EngineMetrics metrics = new EngineMetrics(16); IrisWorld world = IrisWorld.builder().minHeight(0).maxHeight(worldHeight).build(); doReturn(data).when(engine).getData(); doReturn(dimension).when(engine).getDimension(); doReturn(seedManager).when(engine).getSeedManager(); + doReturn(CARVE_SEED).when(seedManager).getCarve(); doReturn(metrics).when(engine).getMetrics(); doReturn(world).when(engine).getWorld(); doReturn(sampledHeight).when(engine).getHeight(anyInt(), anyInt()); @@ -1124,10 +1126,10 @@ public class IrisCaveCarver3DNearParityTest { profile.setVerticalRange(new IrisRange(0D, 120D)); profile.setVerticalEdgeFade(14); profile.setVerticalEdgeFadeStrength(0.21D); - profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.07D)); - profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.17D)); - profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.12D)); - profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.09D)); + profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.109375D)); + profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.265625D)); + profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.1875D)); + profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.140625D)); profile.setBaseWeight(1D); profile.setDetailWeight(0.48D); profile.setWarpStrength(warp ? 0.37D : 0D); @@ -1146,14 +1148,14 @@ public class IrisCaveCarver3DNearParityTest { if (modules) { KList caveModules = new KList<>(); caveModules.add(new IrisCaveFieldModule( - new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.11D), + new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.171875D), 0.23D, 0.04D, new IrisRange(0D, 72D), false )); caveModules.add(new IrisCaveFieldModule( - new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.19D), + new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.296875D), 0.17D, -0.06D, new IrisRange(24D, 120D), diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureCarveEnvelopeTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureCarveEnvelopeTest.java index d25ce12d8..b670f3fbe 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureCarveEnvelopeTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/components/StructureCarveEnvelopeTest.java @@ -2,6 +2,7 @@ package art.arcane.iris.engine.mantle.components; import art.arcane.iris.engine.object.IrisStructureCarveShape; import art.arcane.iris.util.project.noise.CNG; +import art.arcane.iris.util.project.noise.NoiseType; import art.arcane.volmlib.util.math.RNG; import org.junit.Test; @@ -99,7 +100,7 @@ public class StructureCarveEnvelopeTest { @Test public void lobedSideReachWandersInsideTheConfiguredPaddingBand() { - CNG lobe = new CNG(new RNG(1337L), 1D, 1); + CNG lobe = new CNG(new RNG(1337L), NoiseType.CLOVER, 1D, 1); double lowest = 14D; double highest = 0D; 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 index c1a6520a0..e93ec5a6e 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisMathNoiseHotPathParityTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisMathNoiseHotPathParityTest.java @@ -17,10 +17,10 @@ public class IrisMathNoiseHotPathParityTest { 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); + assertEquals(0.4924242426867459D, randomSize.noise(12.5D, -8.25D), 0D); + assertEquals(0.4856492100555178D, randomSize.noise(-31.75D, 44.5D, 9.125D), 0D); + assertEquals(0.3769400526313378D, james.noise(12.5D, -8.25D), 0D); + assertEquals(0.5487488096537423D, james.noise(-31.75D, 44.5D, 9.125D), 0D); } @Test @@ -33,17 +33,17 @@ public class IrisMathNoiseHotPathParityTest { .setStyle(NoiseStyle.PERLIN_IRIS.style()) .setExponent(1.15D); - assertEquals(0.3886291844359823D, noiseGenerator.getNoise(9988L, 31.25D, -17.5D, null), 0D); + assertEquals(0.5084984235498856D, 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); + assertEquals(0.6224926166486215D, 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.5671980255964687D, generator.getHeight(63.0D, -27.0D, 445566L), 0D); + assertEquals(0.43997118860461754D, generator.getHeight(63.0D, -27.0D, 445566L), 0D); } @Test diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorCacheTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorCacheTest.java index f0831d23b..3527c86d4 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorCacheTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorCacheTest.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -78,13 +79,11 @@ public final class IrisNoiseGeneratorCacheTest { private GeneratorFixture() { when(data.getEngine()).thenReturn(engine); - when(firstGenerator.oct(OCTAVES)).thenReturn(firstGenerator); - when(secondGenerator.oct(OCTAVES)).thenReturn(secondGenerator); Map generators = Map.of( 11L + GENERATOR_SALT - CONFIGURED_SEED, firstGenerator, 29L + GENERATOR_SALT - CONFIGURED_SEED, secondGenerator ); - when(style.createNoCache(any(RNG.class), same(data))).thenAnswer(invocation -> { + when(style.createForLayer(any(RNG.class), same(data), eq(OCTAVES))).thenAnswer(invocation -> { RNG rng = invocation.getArgument(0); return generators.get(rng.getSeed()); }); diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorRemapTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorRemapTest.java new file mode 100644 index 000000000..b073da9a6 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisNoiseGeneratorRemapTest.java @@ -0,0 +1,19 @@ +package art.arcane.iris.engine.object; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IrisNoiseGeneratorRemapTest { + @Test + public void parametricCurvePreservesEndpointsAndShapesBothHalves() { + double[][] samples = {{0D, 0D}, {0.25D, 0.1D}, {0.5D, 0.5D}, {0.75D, 0.9D}, {1D, 1D}}; + for (double[] sample : samples) { + IrisNoiseGenerator layer = new IrisNoiseGenerator() + .setStyle(NoiseStyle.FLAT.style()) + .setOpacity(sample[0]) + .setParametric(true); + assertEquals(sample[1], layer.getNoise(75L, 13D, -21D, null), 0.000000000001D); + } + } +} diff --git a/core/src/test/java/art/arcane/iris/engine/object/NoiseStyleContractTest.java b/core/src/test/java/art/arcane/iris/engine/object/NoiseStyleContractTest.java new file mode 100644 index 000000000..1c59b7f2f --- /dev/null +++ b/core/src/test/java/art/arcane/iris/engine/object/NoiseStyleContractTest.java @@ -0,0 +1,201 @@ +package art.arcane.iris.engine.object; + +import art.arcane.iris.core.loader.IrisData; +import art.arcane.iris.util.project.noise.CNG; +import art.arcane.iris.util.project.noise.NoiseGenerator; +import art.arcane.iris.util.project.noise.NoiseType; +import art.arcane.volmlib.util.math.RNG; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public final class NoiseStyleContractTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void builtInStylesAreFiniteAndRepeatableAcrossWorldCoordinates() { + double[] coordinates = {-29_999_983.25D, -513D, -64D, -1D, 0D, 0.125D, 17D, 64D, 8192D, 29_999_983.25D}; + for (NoiseStyle style : NoiseStyle.values()) { + CNG first = style.create(new RNG(813753L)); + CNG second = style.create(new RNG(813753L)); + for (double coordinate : coordinates) { + double x = coordinate; + double z = coordinate * -0.713D; + double[] samples = {first.noise(x), first.noise(x, z), first.noise(x, 81.25D, z)}; + for (double sample : samples) { + assertTrue(style + " is non-finite at " + coordinate, Double.isFinite(sample)); + } + assertEquals(style.name(), samples[0], second.noise(x), 0D); + assertEquals(style.name(), samples[1], second.noise(x, z), 0D); + assertEquals(style.name(), samples[2], second.noise(x, 81.25D, z), 0D); + } + } + } + + @Test + public void classicPresetsShareOneBaseScale() { + NoiseStyle[] styles = {NoiseStyle.SIMPLEX, NoiseStyle.PERLIN, NoiseStyle.CUBIC, + NoiseStyle.FRACTAL_CUBIC, NoiseStyle.BIOCTAVE_SIMPLEX, NoiseStyle.VIGOCTAVE_SIMPLEX, + NoiseStyle.BIOCTAVE_FRACTAL_CUBIC, NoiseStyle.QUADOCTAVE_FRACTAL_CUBIC}; + for (NoiseStyle style : styles) { + CNG generator = style.create(new RNG(7751L)); + NoiseGenerator source = generator.getGenerator(); + double scale = 100D / 64D; + assertEquals(style.name(), source.noise(27.5D * scale, -83.25D * scale), + generator.noise(27.5D, -83.25D), 0D); + } + assertEquals(2D / 64D, NoiseType.HEXAGON.getCoordinateScale(), 0D); + assertEquals(8D / 64D, NoiseType.SIERPINSKI_TRIANGLE.getCoordinateScale(), 0D); + } + + @Test + public void explicitNativeGeneratorFrequenciesKeepTheirUnits() { + CNG generator = new CNG(new RNG(7231L), NoiseType.SIMPLEX, 1D, 1).scale(0.375D); + assertEquals(generator.getGenerator().noise(28D * 0.375D, -74D * 0.375D), + generator.noise(28D, -74D), 0D); + } + + @Test + public void explicitZoomDoublesFeatureSize() { + for (NoiseStyle style : NoiseStyle.values()) { + if (style == NoiseStyle.STATIC) { + continue; + } + CNG normal = style.style().createNoCache(new RNG(45231L), null); + CNG enlarged = style.style().zoomed(2D).createNoCache(new RNG(45231L), null); + assertEquals(style.name(), normal.noise(13.25D, -17.75D), + enlarged.noise(26.5D, -35.5D), 0.000000001D); + assertEquals(style.name(), normal.noise(13.25D), enlarged.noise(26.5D), 0.000000001D); + assertEquals(style.name(), normal.noise(13.25D, 41.125D, -17.75D), + enlarged.noise(26.5D, 82.25D, -35.5D), 0.000000001D); + } + } + + @Test + public void defaultSettingsPreserveEveryPreset() { + for (NoiseStyle style : NoiseStyle.values()) { + CNG direct = style.create(new RNG(7253L)); + CNG configured = style.style().createNoCache(new RNG(7253L), null); + for (int index = 0; index < 32; index++) { + double x = index * 19.713D - 400D; + double z = index * -31.579D + 100D; + assertEquals(style.name(), direct.noise(x, z), configured.noise(x, z), 0D); + assertEquals(style.name(), direct.noise(x, 32.75D, z), configured.noise(x, 32.75D, z), 0D); + } + } + } + + @Test + public void builtInStylesStayInsideTheirUnitRange() { + for (NoiseStyle style : NoiseStyle.values()) { + for (long seed : new long[]{0L, 813753L, Long.MAX_VALUE}) { + CNG generator = style.create(new RNG(seed)); + for (int index = 0; index < 256; index++) { + double x = index * 17.12345D - 2000D; + double z = index * -27.98765D + 3000D; + double[] values = {generator.noise(x), generator.noise(x, z), generator.noise(x, 53.75D, z)}; + for (double value : values) { + assertTrue(style + " produced " + value, value >= 0D && value <= 1D); + } + } + } + } + } + + @Test + public void explicitFractureAndCellularizationZoomWithTheirSource() { + IrisGeneratorStyle[] styles = { + NoiseStyle.SIMPLEX.style().setFracture(NoiseStyle.CLOVER.style().setMultiplier(50D).setZoom(1.5D)), + NoiseStyle.IRIS.style().setCellularFrequency(0.7D).setCellularZoom(1.25D) + }; + for (IrisGeneratorStyle style : styles) { + CNG normal = style.createNoCache(new RNG(217L), null); + CNG enlarged = style.setZoom(2D).createNoCache(new RNG(217L), null); + assertEquals(normal.noise(19.25D, -71.75D), enlarged.noise(38.5D, -143.5D), 0.000000001D); + } + } + + @Test + public void configuredExponentCompoundsThePresetCurve() { + CNG base = NoiseStyle.VASCULAR_THIN.style().createNoCache(new RNG(8831L), null); + CNG squared = NoiseStyle.VASCULAR_THIN.style().setExponent(2D).createNoCache(new RNG(8831L), null); + double value = base.noise(29.5D, -33.25D); + assertEquals(value * value, squared.noise(29.5D, -33.25D), 0.000000000001D); + } + + @Test + public void octavePresetsKeepTheirBaseSeed() { + NoiseStyle[][] families = { + {NoiseStyle.SIMPLEX, NoiseStyle.BIOCTAVE_SIMPLEX}, + {NoiseStyle.FRACTAL_FBM_SIMPLEX, NoiseStyle.BIOCTAVE_FRACTAL_FBM_SIMPLEX}, + {NoiseStyle.FRACTAL_BILLOW_SIMPLEX, NoiseStyle.BIOCTAVE_FRACTAL_BILLOW_SIMPLEX}, + {NoiseStyle.FRACTAL_RM_SIMPLEX, NoiseStyle.BIOCTAVE_FRACTAL_RM_SIMPLEX}, + {NoiseStyle.FRACTAL_BILLOW_PERLIN, NoiseStyle.BIOCTAVE_FRACTAL_BILLOW_PERLIN}, + {NoiseStyle.FRACTAL_CUBIC, NoiseStyle.BIOCTAVE_FRACTAL_CUBIC} + }; + for (NoiseStyle[] family : families) { + CNG configured = family[0].create(new RNG(82731L)).oct(2); + CNG preset = family[1].create(new RNG(82731L)); + for (int index = 0; index < 32; index++) { + double x = index * 7.125D; + double z = index * -11.875D; + assertEquals(family[1].name(), configured.noise(x, z), preset.noise(x, z), 0D); + assertEquals(family[1].name(), configured.noise(x, 31.25D, z), preset.noise(x, 31.25D, z), 0D); + } + } + } + + @Test + public void layerOctavesPreserveAndMultiplyPresetDetail() { + IrisGeneratorStyle style = NoiseStyle.QUADOCTAVE_SIMPLEX.style(); + CNG unchanged = style.createForLayer(new RNG(71L), null, 1); + CNG detailed = style.createForLayer(new RNG(71L), null, 2); + assertEquals(4, unchanged.getOct()); + assertEquals(8, detailed.getOct()); + assertEquals(16, style.createForLayer(new RNG(71L), null, Integer.MAX_VALUE).getOct()); + assertNotEquals(unchanged.noise(21D, -31D), detailed.noise(21D, -31D), 0D); + } + + @Test + public void cachedLayersRetainTheirOctavesAcrossReload() throws Exception { + File dataFolder = folder.newFolder("pack"); + IrisData data = mock(IrisData.class); + when(data.getDataFolder()).thenReturn(dataFolder); + IrisGeneratorStyle cached = NoiseStyle.BIOCTAVE_SIMPLEX.style().setCacheSize(32); + CNG first = cached.createForLayer(new RNG(921L), data, 3); + CNG reloaded = cached.createForLayer(new RNG(921L), data, 3); + CNG uncached = NoiseStyle.BIOCTAVE_SIMPLEX.style().createForLayer(new RNG(921L), data, 3); + CNG different = cached.createForLayer(new RNG(921L), data, 1); + assertEquals(6, first.getOct()); + assertEquals(6, reloaded.getOct()); + assertEquals(uncached.noise(4D, 7D), first.noise(4D, 7D), 0.000001D); + assertEquals(first.noise(4D, 7D), reloaded.noise(4D, 7D), 0D); + assertNotEquals(first.noise(4D, 7D), different.noise(4D, 7D), 0D); + File[] caches = new File(dataFolder, ".cache").listFiles((parent, name) -> name.endsWith(".cnm")); + assertEquals(2, caches.length); + } + + @Test + public void invalidTransformsFailBeforeSampling() { + double[] invalid = {0D, -1D, Double.NaN, Double.POSITIVE_INFINITY, Double.MIN_VALUE}; + for (double value : invalid) { + assertThrows(IllegalArgumentException.class, + () -> NoiseStyle.SIMPLEX.style().setZoom(value).createNoCache(new RNG(1L), null)); + assertThrows(IllegalArgumentException.class, + () -> NoiseStyle.SIMPLEX.style().setExponent(value).createNoCache(new RNG(1L), null)); + assertThrows(IllegalArgumentException.class, + () -> NoiseStyle.SIMPLEX.style().setCellularFrequency(1D).setCellularZoom(value) + .createNoCache(new RNG(1L), null)); + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationBoundsTest.java b/core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationBoundsTest.java index 03ce2d73d..ae57b3ead 100644 --- a/core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationBoundsTest.java +++ b/core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationBoundsTest.java @@ -37,6 +37,26 @@ public class IrisInterpolationBoundsTest { } } + @Test + public void fractionalBoundsMatchSeparatePassesAndSampleEachCoordinateOnce() { + for (InterpolationMethod method : InterpolationMethod.values()) { + Map expectedSamples = new HashMap<>(); + NoiseBounds expected = separatePasses(method, -31.75D, 29.25D, 32D, + IrisInterpolationBoundsTest::asymmetricBounds, expectedSamples); + Map actualSamples = new HashMap<>(); + NoiseBounds actual = IrisInterpolation.getNoiseBounds(method, -31.75D, 29.25D, 32D, (x, z) -> { + actualSamples.merge(new Coordinate(x, z), 1, Integer::sum); + return asymmetricBounds(x, z); + }); + + assertBounds(expected, actual); + assertEquals(expectedSamples.keySet(), actualSamples.keySet()); + for (int calls : actualSamples.values()) { + assertEquals(1, calls); + } + } + } + @Test public void nestedProvidersKeepTheirOwnSamplingSequences() { for (InterpolationMethod method : InterpolationMethod.values()) { @@ -95,7 +115,7 @@ public class IrisInterpolationBoundsTest { assertBounds(expected, actual); } - private static NoiseBounds separatePasses(InterpolationMethod method, int x, int z, double scale, + private static NoiseBounds separatePasses(InterpolationMethod method, double x, double z, double scale, NoiseBoundsProvider provider, Map samples) { double minimum = IrisInterpolation.getNoise(method, x, z, scale, (sx, sz) -> samples.computeIfAbsent(new Coordinate(sx, sz), diff --git a/core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationSamplingTest.java b/core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationSamplingTest.java new file mode 100644 index 000000000..61fff6693 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/interpolation/IrisInterpolationSamplingTest.java @@ -0,0 +1,168 @@ +package art.arcane.iris.util.project.interpolation; + +import art.arcane.volmlib.util.function.NoiseProvider; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class IrisInterpolationSamplingTest { + @Test + public void bicubicDispatchUsesSixteenSamplesAndTheCubicKernel() { + NoiseProvider source = (x, z) -> x * x + z * z * z; + AtomicInteger calls = new AtomicInteger(); + NoiseProvider countingSource = (x, z) -> { + calls.incrementAndGet(); + return source.noise(x, z); + }; + double expected = IrisInterpolation.getBicubicNoise(3, 5, 8D, source); + double bilinear = IrisInterpolation.getBilinearNoise(3, 5, 8D, source); + double actual = IrisInterpolation.getNoise(InterpolationMethod.BICUBIC, 3, 5, 8D, countingSource); + + assertEquals(expected, actual, 0D); + assertNotEquals(bilinear, actual, 1.0E-12D); + assertEquals(16, calls.get()); + } + + @Test + public void bilinearSamplingPreservesFractionalAndNegativeCoordinates() { + NoiseProvider source = (x, z) -> x * 0.001D + z * 0.002D; + + for (double x : new double[]{-31.75D, -0.25D, 0D, 0.75D, 31.25D, 29_999_999.25D}) { + for (double z : new double[]{-33.5D, -0.5D, 0.5D, 23.75D}) { + assertEquals(source.noise(x, z), + IrisInterpolation.getNoise(InterpolationMethod.BILINEAR, x, z, 32D, source), 1.0E-11D); + } + } + + assertEquals(-1, IrisInterpolation.getRadiusFactor(-0.25D, 2.5D)); + assertEquals(0, IrisInterpolation.getRadiusFactor(2.25D, 2.5D)); + assertEquals(1, IrisInterpolation.getRadiusFactor(2.75D, 2.5D)); + } + + @Test + public void starcastPreservesFractionalCentersNearTheWorldBorder() { + double x = 30_000_000.25D; + double z = -29_999_999.75D; + List samples = new ArrayList<>(); + IrisInterpolation.getNoise(InterpolationMethod.STARCAST_3, x, z, 8D, (sampleX, sampleZ) -> { + samples.add(new Coordinate(sampleX, sampleZ)); + return 0.5D; + }); + + assertEquals(3, samples.size()); + for (int index = 0; index < samples.size(); index++) { + double angle = index * Math.PI * 2D / 3D; + double dx = 8D * (Math.cos(angle) - Math.sin(angle)); + double dz = 8D * (Math.sin(angle) + Math.cos(angle)); + assertEquals(x + dx, samples.get(index).x, 1.0E-12D); + assertEquals(z + dz, samples.get(index).z, 1.0E-12D); + } + } + + @Test + public void compositeSampleCachesKeepFractionalCentersSeparate() { + NoiseProvider source = (x, z) -> Math.sin(x * 0.013D) + Math.cos(z * 0.021D); + + for (double x : new double[]{-31.75D, -0.25D, 0.25D, 31.75D}) { + double z = x + 7.5D; + Map samples = new HashMap<>(); + double expected = IrisInterpolation.getNoise(InterpolationMethod.STARCAST_12, x, z, 32D, + (sampleX, sampleZ) -> IrisInterpolation.getBilinearNoise(sampleX, sampleZ, 32D, source)); + double actual = IrisInterpolation.getNoise(InterpolationMethod.BILINEAR_STARCAST_12, + x, z, 32D, (sampleX, sampleZ) -> { + samples.merge(new Coordinate(sampleX, sampleZ), 1, Integer::sum); + return source.noise(sampleX, sampleZ); + }); + + assertEquals(expected, actual, 0D); + for (int calls : samples.values()) { + assertEquals(1, calls); + } + } + } + + @Test + public void fractionalGridSpacingDoesNotCollapseOrJumpAtCellBoundaries() { + NoiseProvider source = (x, z) -> Math.sin(x * 0.73D) * 0.4D + Math.cos(z * 0.59D) * 0.2D; + + for (InterpolationMethod method : InterpolationMethod.values()) { + for (double radius : new double[]{0.125D, 0.375D, 2.5D, 7.75D}) { + for (int cell : new int[]{-7, -1, 0, 1, 7}) { + double boundary = cell * radius; + double center = IrisInterpolation.getNoise(method, boundary, 0.37D, radius, source); + double before = IrisInterpolation.getNoise(method, boundary - 1.0E-8D, 0.37D, radius, source); + double after = IrisInterpolation.getNoise(method, boundary + 1.0E-8D, 0.37D, radius, source); + + assertTrue(method.name(), Double.isFinite(center)); + assertEquals(method.name(), center, before, 1.0E-6D); + assertEquals(method.name(), center, after, 1.0E-6D); + } + } + } + } + + @Test + public void gridSamplesKeepTheConfiguredFractionalSpacing() { + List samples = new ArrayList<>(); + IrisInterpolation.getBilinearNoise(-0.25D, 0.125D, 0.375D, (x, z) -> { + samples.add(new Coordinate(x, z)); + return x + z; + }); + + assertEquals(List.of(new Coordinate(-0.375D, 0D), new Coordinate(0D, 0D), + new Coordinate(-0.375D, 0.375D), new Coordinate(0D, 0.375D)), samples); + } + + @Test + public void starcastPreservesDoubleSamplePrecisionAndLinearFields() { + NoiseProvider source = (x, z) -> 0.125D + x * 1.0E-11D - z * 2.0E-11D; + + for (InterpolationMethod method : new InterpolationMethod[]{InterpolationMethod.STARCAST_3, + InterpolationMethod.STARCAST_6, InterpolationMethod.STARCAST_9, InterpolationMethod.STARCAST_12}) { + assertEquals(method.name(), source.noise(0.25D, -0.75D), + IrisInterpolation.getNoise(method, 0.25D, -0.75D, 32.00000001D, source), 1.0E-16D); + assertNotEquals(method.name(), + IrisInterpolation.getNoise(method, 0D, 0D, 32D, source), + IrisInterpolation.getNoise(method, 0.25D, -0.75D, 32D, source), 1.0E-13D); + } + } + + @Test + public void parametricCurvesHaveSymmetricMidpointsAndMonotonicEndpoints() { + for (double alpha : new double[]{1D, 1.5D, 2D, 4D}) { + assertEquals(0D, IrisInterpolation.parametric(0D, alpha), 0D); + assertEquals(0.5D, IrisInterpolation.parametric(0.5D, alpha), 0D); + assertEquals(1D, IrisInterpolation.parametric(1D, alpha), 0D); + double previous = 0D; + for (int sample = 1; sample <= 100; sample++) { + double t = sample / 100D; + double value = IrisInterpolation.parametric(t, alpha); + assertTrue(value >= previous); + assertTrue(value <= 1D); + assertEquals(value, 1D - IrisInterpolation.parametric(1D - t, alpha), 1.0E-15D); + previous = value; + } + } + } + + @Test + public void catmullRomKeepsLinearSlopesAcrossGridBoundaries() { + NoiseProvider source = (x, z) -> x * 0.7D - z * 0.3D; + for (double x : new double[]{-7.75D, -0.25D, 0.25D, 7.75D}) { + assertEquals(source.noise(x, 2.75D), + IrisInterpolation.getNoise(InterpolationMethod.CATMULL_ROM_SPLINE, x, 2.75D, 8D, source), + 1.0E-12D); + } + } + + private record Coordinate(double x, double z) { + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CNGDimensionDispatchTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CNGDimensionDispatchTest.java new file mode 100644 index 000000000..d0c9a5b4e --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CNGDimensionDispatchTest.java @@ -0,0 +1,260 @@ +package art.arcane.iris.util.project.noise; + +import art.arcane.volmlib.util.math.RNG; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; + +public class CNGDimensionDispatchTest { + private static final double EPSILON = 1.0E-12D; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void eachEntrypointUsesItsNativeDimensionWithScalingAndOpacity() { + CoordinateNoise source = new CoordinateNoise(); + CNG noise = new CNG(new RNG(41L), source, 0.6D, 1).scale(0.375D).bake().scale(1.75D); + double scale = 0.375D * 1.75D; + + for (double x : new double[]{-13.25D, 0D, 17.5D}) { + double z = x + 2.75D; + double y = x - 4.125D; + double expected1D = source.noise(x * scale) * 0.6D; + double expected2D = source.noise(x * scale, z * scale) * 0.6D; + double expected3D = source.noise(x * scale, y * scale, z * scale) * 0.6D; + + assertEquals(expected1D, noise.noise(x), EPSILON); + assertEquals(expected1D, noise.noiseFast1D(x), EPSILON); + assertEquals(expected1D, noise.noise(new double[]{x}), EPSILON); + assertEquals(expected2D, noise.noise(x, z), EPSILON); + assertEquals(expected2D, noise.noiseFast2D(x, z), EPSILON); + assertEquals(expected2D, noise.noise(new double[]{x, z}), EPSILON); + assertEquals(expected2D * 2D - 1D, noise.noiseFastSigned2D(x, z), EPSILON); + assertEquals(expected3D, noise.noise(x, y, z), EPSILON); + assertEquals(expected3D, noise.noiseFast3D(x, y, z), EPSILON); + assertEquals(expected3D, noise.noise(new double[]{x, y, z}), EPSILON); + assertEquals(expected3D * 2D - 1D, noise.noiseFastSigned3D(x, y, z), EPSILON); + } + } + + @Test + public void generatorsWithoutCoordinateScalingKeepTheirNativeInputs() { + CoordinateNoise source = new CoordinateNoise() { + @Override + public boolean isNoScale() { + return true; + } + }; + CNG fracture = new CNG(new RNG(2L), new CoordinateNoise(), 1D, 1); + CNG noise = new CNG(new RNG(1L), source, 1D, 1).scale(7D).bake().fractureWith(fracture, 12D); + + assertEquals(source.noise(3D), noise.noiseFast1D(3D), EPSILON); + assertEquals(source.noise(3D, 7D), noise.noiseFast2D(3D, 7D), EPSILON); + assertEquals(source.noiseSigned(3D, 7D), noise.noiseFastSigned2D(3D, 7D), EPSILON); + assertEquals(source.noise(3D, 5D, 7D), noise.noiseFast3D(3D, 5D, 7D), EPSILON); + assertEquals(source.noiseSigned(3D, 5D, 7D), noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON); + } + + @Test + public void fractureSamplesAndDisplacesTheRequestedDimensions() { + CoordinateNoise source = new CoordinateNoise(); + CoordinateNoise fractureSource = new CoordinateNoise(); + CNG fracture = new CNG(new RNG(2L), fractureSource, 1D, 1); + CNG noise = new CNG(new RNG(1L), source, 0.7D, 1).scale(0.35D).fractureWith(fracture, 12D); + double x = -13.25D; + double y = 5.5D; + double z = 37.75D; + double displaced1D = x + (fractureSource.noise(x) - 0.5D) * 12D; + double displacedX2D = x + (fractureSource.noise(x, z) - 0.5D) * 12D; + double displacedZ2D = z + (fractureSource.noise(z, x) - 0.5D) * 12D; + double displacedX3D = x + (fractureSource.noise(x, y, z) - 0.5D) * 12D; + double displacedY3D = y + (fractureSource.noise(y, x) - 0.5D) * 12D; + double displacedZ3D = z + (fractureSource.noise(z, x, y) - 0.5D) * 12D; + double expected1D = source.noise(displaced1D * 0.35D) * 0.7D; + double expected2D = source.noise(displacedX2D * 0.35D, displacedZ2D * 0.35D) * 0.7D; + double expected3D = source.noise(displacedX3D * 0.35D, displacedY3D * 0.35D, displacedZ3D * 0.35D) * 0.7D; + + assertEquals(expected1D, noise.noiseFast1D(x), EPSILON); + assertEquals(expected1D, noise.noise(new double[]{x}), EPSILON); + assertEquals(expected2D, noise.noiseFast2D(x, z), EPSILON); + assertEquals(expected2D, noise.noise(new double[]{x, z}), EPSILON); + assertEquals(expected2D * 2D - 1D, noise.noiseFastSigned2D(x, z), EPSILON); + assertEquals(expected3D, noise.noiseFast3D(x, y, z), EPSILON); + assertEquals(expected3D, noise.noise(new double[]{x, y, z}), EPSILON); + assertEquals(expected3D * 2D - 1D, noise.noiseFastSigned3D(x, y, z), EPSILON); + } + + @Test + public void offsetsPreserveTheNativeCoordinatePlane() { + long seed = 75L; + RNG random = new RNG(seed); + double offsetX = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE); + double offsetZ = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE); + CoordinateNoise source = new CoordinateNoise(); + CNG noise = new CNG(new RNG(9L), source.offset(seed), 1D, 1); + + assertEquals(source.noise(3D + offsetX), noise.noise(3D), EPSILON); + assertEquals(source.noise(3D + offsetX, 7D + offsetZ), noise.noise(3D, 7D), EPSILON); + assertEquals(source.noiseSigned(3D + offsetX, 7D + offsetZ), noise.noiseFastSigned2D(3D, 7D), EPSILON); + assertEquals(source.noise(3D + offsetX, 5D, 7D + offsetZ), noise.noise(3D, 5D, 7D), EPSILON); + } + + @Test + public void zoomResizesSharedFracturesAndChildrenOnce() { + CNG shared = new CNG(new RNG(71L), new CoordinateNoise(), 1D, 1).scale(0.7D); + CNG noise = new CNG(new RNG(73L), new CoordinateNoise(), 1D, 1) + .scale(0.8D).fractureWith(shared, 3D).child(shared); + double one = noise.noise(7D); + double two = noise.noise(7D, -11D); + double three = noise.noise(7D, 17D, -11D); + noise.zoom(2D); + assertEquals(one, noise.noise(14D), EPSILON); + assertEquals(two, noise.noise(14D, -22D), EPSILON); + assertEquals(three, noise.noise(14D, 34D, -22D), EPSILON); + assertEquals(two * 2D - 1D, noise.noiseFastSigned2D(14D, -22D), EPSILON); + assertEquals(three * 2D - 1D, noise.noiseFastSigned3D(14D, 34D, -22D), EPSILON); + } + + @Test + public void zoomInvalidatesBakedAndSignedSamples() { + CoordinateNoise source = new CoordinateNoise(); + CNG noise = new CNG(new RNG(31L), source, 1D, 1); + noise.cached(4, "zoom", temporaryFolder.getRoot(), true); + noise.noiseFastSigned2D(2D, 2D); + noise.zoom(2D); + assertEquals(source.noise(1D, 1D), noise.noise(2D, 2D), EPSILON); + assertEquals(source.noiseSigned(1D, 1D), noise.noiseFastSigned2D(2D, 2D), EPSILON); + for (double factor : new double[]{0D, -1D, Double.NaN, Double.POSITIVE_INFINITY, Double.MIN_VALUE}) { + assertThrows(IllegalArgumentException.class, () -> noise.zoom(factor)); + } + } + + @Test + public void cachedIntegerAndUncachedFractionalSamplesUseTheSamePlane() { + CoordinateNoise source = new CoordinateNoise(); + CNG noise = new CNG(new RNG(15L), source, 1D, 1); + noise.noiseFastSigned2D(1D, 2D); + noise.cached(4, "dimensions", temporaryFolder.getRoot(), true); + double cached = (float) source.noise(1D, 2D); + + assertEquals(cached, noise.noise(1D, 2D), 0D); + assertEquals(cached, noise.noiseFast2D(1D, 2D), 0D); + assertEquals(cached, noise.noise(new double[]{1D, 2D}), 0D); + assertEquals(cached * 2D - 1D, noise.noiseFastSigned2D(1D, 2D), 0D); + assertEquals(source.noise(1.25D, 2.5D), noise.noiseFast2D(1.25D, 2.5D), EPSILON); + assertEquals(source.noise(1D), noise.noise(1D), EPSILON); + assertEquals(source.noise(1D, 3D, 2D), noise.noise(1D, 3D, 2D), EPSILON); + + noise.scale(0.5D); + assertEquals(source.noise(0.5D, 1D), noise.noiseFast2D(1D, 2D), EPSILON); + assertEquals(source.noiseSigned(0.5D, 1D), noise.noiseFastSigned2D(1D, 2D), EPSILON); + } + + @Test + public void cachedNoiseDoesNotMirrorOrRepeatOutsideItsBakedArea() { + CNG uncached = new CNG(new RNG(15L), new CoordinateNoise(), 1D, 1); + CNG cached = new CNG(new RNG(15L), new CoordinateNoise(), 1D, 1); + cached.cached(4, "bounded-dimensions", temporaryFolder.getRoot(), true); + double[] coordinates = {-8D, -4D, -1D, -0.25D, 0D, 0.25D, 3D, 3.75D, 4D, 4.25D, 8D, 29_999_999D}; + + for (double x : coordinates) { + assertEquals(uncached.noise(x), cached.noise(x), 0D); + assertEquals(uncached.noiseFast1D(x), cached.noiseFast1D(x), 0D); + assertEquals(uncached.noise(new double[]{x}), cached.noise(new double[]{x}), 0D); + + for (double z : coordinates) { + double expected = uncached.noise(x, z); + if (x >= 0D && x < 4D && z >= 0D && z < 4D && x == (int) x && z == (int) z) { + expected = (float) expected; + } + + assertEquals(expected, cached.noise(x, z), 0D); + assertEquals(expected, cached.noiseFast2D(x, z), 0D); + assertEquals(expected, cached.noise(new double[]{x, z}), 0D); + assertEquals(expected * 2D - 1D, cached.noiseFastSigned2D(x, z), EPSILON); + assertEquals(uncached.noise(x, 5D, z), cached.noise(x, 5D, z), 0D); + assertEquals(uncached.noiseFast3D(x, 5D, z), cached.noiseFast3D(x, 5D, z), 0D); + assertEquals(uncached.noiseFastSigned3D(x, 5D, z), cached.noiseFastSigned3D(x, 5D, z), 0D); + assertEquals(uncached.noise(new double[]{x, 5D, z}), cached.noise(new double[]{x, 5D, z}), 0D); + } + } + } + + @Test + public void octaveChangesReachNestedOffsetsAndInvalidateSignedSamples() { + OctaveCoordinateNoise source = new OctaveCoordinateNoise(); + OffsetNoiseGenerator offset = source.offset(6L).offset(9L); + CNG noise = new CNG(new RNG(77L), offset, 1D, 3); + + assertEquals(3, source.octaves); + double previous2D = noise.noiseFastSigned2D(3D, 7D); + double previous3D = noise.noiseFastSigned3D(3D, 5D, 7D); + noise.oct(5); + + assertEquals(5, source.octaves); + assertEquals(offset.noiseSigned(3D, 7D), noise.noiseFastSigned2D(3D, 7D), EPSILON); + assertEquals(offset.noiseSigned(3D, 5D, 7D), noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON); + assertNotEquals(previous2D, noise.noiseFastSigned2D(3D, 7D), EPSILON); + assertNotEquals(previous3D, noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON); + } + + @Test + public void transformChangesInvalidateSignedSamples() { + CNG noise = new CNG(new RNG(34L), new CoordinateNoise(), 1D, 1); + double previous2D = noise.noiseFastSigned2D(3D, 7D); + double previous3D = noise.noiseFastSigned3D(3D, 5D, 7D); + noise.up(0.125D); + + assertEquals(previous2D + 0.25D, noise.noiseFastSigned2D(3D, 7D), EPSILON); + assertEquals(previous3D + 0.25D, noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON); + } + + @Test + public void invalidDimensionCountsFailExplicitly() { + CNG noise = new CNG(new RNG(9L), new CoordinateNoise(), 1D, 1); + + assertThrows(IllegalArgumentException.class, () -> noise.noise(new double[0])); + assertThrows(IllegalArgumentException.class, () -> noise.noise(new double[]{1D, 2D, 3D, 4D})); + } + + private static class CoordinateNoise implements NoiseGenerator { + @Override + public double noise(double x) { + return 0.125D + x * 0.001D; + } + + @Override + public double noise(double x, double z) { + return 0.25D + x * 0.001D + z * 0.002D; + } + + @Override + public double noise(double x, double y, double z) { + return 0.5D + x * 0.001D + y * 0.002D + z * 0.003D; + } + } + + private static final class OctaveCoordinateNoise extends CoordinateNoise implements OctaveNoise { + private int octaves; + + @Override + public double noise(double x, double z) { + return super.noise(x, z) / octaves; + } + + @Override + public double noise(double x, double y, double z) { + return super.noise(x, y, z) / octaves; + } + + @Override + public void setOctaves(int octaves) { + this.octaves = octaves; + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java index d3c707f7b..49a8a75d4 100644 --- a/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java @@ -126,7 +126,7 @@ public class CNGInjectorParityTest { } private double legacyCombined1D(CompositeFixture fixture, double x) { - double n = fixture.rootGenerator.noise(x, 0D, 0D); + double n = fixture.rootGenerator.noise(x); double m = 1D; double valueA = fixture.childA.noise(x); @@ -143,7 +143,7 @@ public class CNGInjectorParityTest { } private double legacyCombined2D(CompositeFixture fixture, double x, double z) { - double n = fixture.rootGenerator.noise(x, z, 0D); + double n = fixture.rootGenerator.noise(x, z); double m = 1D; double valueA = fixture.childA.noise(x, z); diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CellularNoisePrecisionTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CellularNoisePrecisionTest.java new file mode 100644 index 000000000..bad6c1c0b --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CellularNoisePrecisionTest.java @@ -0,0 +1,57 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CellularNoisePrecisionTest { + @Test + public void distantCellBoundariesRetainSubFloatCoordinatePrecision() { + for (double origin : new double[]{-29_999_000D, 29_998_000D}) { + CellularNoise noise = new CellularNoise(1337L); + for (int dimensions = 1; dimensions <= 3; dimensions++) { + double previousCoordinate = origin; + double previousValue = sample(noise, dimensions, origin); + boolean preciseBoundary = false; + for (int step = 1; step <= 8192; step++) { + double coordinate = origin + step * 0.125D; + double value = sample(noise, dimensions, coordinate); + if (Float.floatToRawIntBits((float) coordinate) + == Float.floatToRawIntBits((float) previousCoordinate) + && value != previousValue) { + preciseBoundary = true; + break; + } + previousCoordinate = coordinate; + previousValue = value; + } + assertTrue("Missing precise cell boundary at " + origin + " in " + dimensions + "D", preciseBoundary); + } + } + } + + @Test + public void distantSamplesAreBoundedAndRepeatableAcrossSeeds() { + for (long seed : new long[]{0L, -1L, Long.MIN_VALUE, Long.MAX_VALUE}) { + CellularNoise first = new CellularNoise(seed); + CellularNoise second = new CellularNoise(seed); + for (int dimensions = 1; dimensions <= 3; dimensions++) { + for (int step = -256; step <= 256; step++) { + double coordinate = Math.copySign(29_999_000D, step) + step * 0.125D; + double value = sample(first, dimensions, coordinate); + assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D); + assertEquals(value, sample(second, dimensions, coordinate), 0D); + } + } + } + } + + private static double sample(CellularNoise noise, int dimensions, double coordinate) { + return switch (dimensions) { + case 1 -> noise.noise(coordinate); + case 2 -> noise.noise(coordinate, -29_998_713.375D); + default -> noise.noise(coordinate, 73.375D, 29_998_713.375D); + }; + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/ClassicNoiseQualityTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/ClassicNoiseQualityTest.java new file mode 100644 index 000000000..f77d90bc6 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/ClassicNoiseQualityTest.java @@ -0,0 +1,178 @@ +package art.arcane.iris.util.project.noise; + +import art.arcane.volmlib.util.math.RNG; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class ClassicNoiseQualityTest { + private static final long SEED = 972453L; + + @Test + public void directCubicUsesConfiguredSeedInBothPrecisions() { + for (long seed : new long[]{1L, -23L, Long.MAX_VALUE}) { + FastNoiseDouble doubles = new FastNoiseDouble(seed); + doubles.setNoiseType(FastNoiseDouble.NoiseType.Cubic); + FastNoise floats = new FastNoise((int) seed); + floats.SetNoiseType(FastNoise.NoiseType.Cubic); + for (int x = -300; x <= 300; x += 37) { + double sampleX = x + 0.193D; + double sampleZ = x * 0.71D + 43.517D; + assertEquals(doubles.GetNoise(sampleX, sampleZ), doubles.GetCubic(sampleX, sampleZ), 0D); + assertEquals(floats.GetNoise((float) sampleX, (float) sampleZ), + floats.GetCubic((float) sampleX, (float) sampleZ), 0F); + } + } + CubicNoise first = new CubicNoise(SEED); + CubicNoise second = new CubicNoise(SEED + 1); + assertNotEquals(first.noise(37.219D), second.noise(37.219D), 1E-8D); + assertNotEquals(first.noise(37.219D, -91.713D), second.noise(37.219D, -91.713D), 1E-8D); + } + + @Test + public void perlinDefaultsToQuinticSmoothing() { + FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax()); + source.setLongerp(FastNoiseDouble.Longerp.Qulongic); + source.setFractalType(FastNoiseDouble.FractalType.Billow); + source.setFractalOctaves(1); + PerlinNoise perlin = new PerlinNoise(SEED); + FractalBillowPerlinNoise billow = new FractalBillowPerlinNoise(SEED); + for (int x = -240; x <= 240; x += 17) { + double sampleX = x + 0.37D; + double sampleZ = x * 0.91D - 8.143D; + assertEquals(source.GetPerlin(sampleX, sampleZ), perlin.noiseSigned(sampleX, sampleZ), 0D); + assertEquals(source.GetPerlinFractal(sampleX, sampleZ) / 2D + 0.5D, + billow.noise(sampleX, sampleZ), 0D); + assertEquals(source.GetPerlin(sampleX, 63.21D, sampleZ), + perlin.noiseSigned(sampleX, 63.21D, sampleZ), 0D); + assertEquals(source.GetPerlinFractal(sampleX, 63.21D, sampleZ) / 2D + 0.5D, + billow.noise(sampleX, 63.21D, sampleZ), 0D); + } + } + + @Test + public void perlinSlopesRemainContinuousAcrossLatticeBoundaries() { + PerlinNoise noise = new PerlinNoise(SEED); + double step = 0.001D; + for (int x = -400; x <= 400; x += 100) { + double center = noise.noiseSigned(x, 37.219D, -91.713D); + double leftSlope = (center - noise.noiseSigned(x - step, 37.219D, -91.713D)) / step; + double rightSlope = (noise.noiseSigned(x + step, 37.219D, -91.713D) - center) / step; + assertEquals(leftSlope, rightSlope, 1E-8D); + } + } + + @Test + public void ridgedSimplexNormalizesTheActualOctaveRange() { + FractalRigidMultiSimplexNoise ridge = new FractalRigidMultiSimplexNoise(SEED); + FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax()); + source.setFractalType(FastNoiseDouble.FractalType.RigidMulti); + for (int octaves : new int[]{1, 2, 4, 8, 16}) { + ridge.setOctaves(octaves); + source.setFractalOctaves(octaves); + double amplitudeSum = 2D - Math.scalb(1D, 1 - octaves); + assertEquals(0D, ridge.f(1D - amplitudeSum), 1E-15D); + assertEquals(1D, ridge.f(1D), 0D); + for (int x = -400; x <= 400; x += 11) { + double sampleX = x + 0.37D; + double sampleZ = x * 0.91D - 8.143D; + double value = ridge.noise(sampleX, sampleZ); + double raw = source.GetSimplexFractal(sampleX, sampleZ); + assertEquals((raw + amplitudeSum - 1D) / amplitudeSum, value, 1E-15D); + assertTrue(value >= 0D && value <= 1D); + assertEquals(value * 2D - 1D, ridge.noiseSigned(sampleX, sampleZ), 0D); + } + } + ridge.setOctaves(1); + assertEquals(1D - Math.abs(source.GetSimplex(37.219D, -91.713D)), + ridge.noise(37.219D, -91.713D), 0D); + } + + @Test + public void classicAndCellularFamiliesRemainBoundedAtWorldEdges() { + NoiseGenerator[] generators = { + new CubicNoise(SEED), new FractalCubicNoise(SEED), new FractalBillowPerlinNoise(SEED), + new FractalBillowSimplexNoise(SEED), new FractalFBMSimplexNoise(SEED), + new FractalRigidMultiSimplexNoise(SEED), new GlobNoise(SEED), new CellHeightNoise(SEED), + new VascularNoise(SEED), new CellularNoise(SEED) + }; + for (NoiseGenerator generator : generators) { + for (int octave : new int[]{1, 4, 16}) { + if (generator instanceof OctaveNoise fractal) { + fractal.setOctaves(octave); + } + for (double origin : new double[]{-30_000_000D, 0D, 30_000_000D}) { + for (int i = 0; i < 64; i++) { + double x = origin + i * 0.317D; + double z = -origin - i * 0.913D; + assertBounded(generator.noise(x)); + assertBounded(generator.noise(x, z)); + assertBounded(generator.noise(x, 71.917D, z)); + } + } + } + } + } + + @Test + public void cellularHeightAndVascularKeepComplementaryProfiles() { + CellHeightNoise height = new CellHeightNoise(SEED); + VascularNoise vascular = new VascularNoise(SEED); + for (int x = -300; x <= 300; x += 13) { + double sampleX = x + 0.91D; + double sampleZ = x * 0.31D - 7.193D; + assertEquals(1D, height.noise(sampleX, sampleZ) + vascular.noise(sampleX, sampleZ), 1E-15D); + assertEquals(1D, height.noise(sampleX, 87.137D, sampleZ) + + vascular.noise(sampleX, 87.137D, sampleZ), 1E-15D); + } + } + + @Test + public void vascularBrightensWhereCellDistancesConverge() { + FastNoiseDouble distance = new FastNoiseDouble(new RNG(SEED).lmax()); + distance.setCellularDistanceFunction(FastNoiseDouble.CellularDistanceFunction.Natural); + distance.setCellularReturnType(FastNoiseDouble.CellularReturnType.Distance2Sub); + CellHeightNoise height = new CellHeightNoise(SEED); + VascularNoise vascular = new VascularNoise(SEED); + int borderSamples = 0; + int interiorSamples = 0; + for (int x = -500; x <= 500; x++) { + double sampleX = x + 0.137D; + double sampleZ = x * 3.17D - 71.913D; + double separation = distance.GetCellular(sampleX, sampleZ) + 1D; + if (separation < 0.02D) { + assertTrue(vascular.noise(sampleX, sampleZ) > 0.99D); + assertTrue(height.noise(sampleX, sampleZ) < 0.01D); + borderSamples++; + } else if (separation > 1D) { + assertTrue(vascular.noise(sampleX, sampleZ) < 0.5D); + assertTrue(height.noise(sampleX, sampleZ) > 0.5D); + interiorSamples++; + } + assertEquals(vascular.noise(sampleX, sampleZ) * 2D - 1D, + vascular.noiseSigned(sampleX, sampleZ), 2E-16D); + } + assertTrue(borderSamples > 0); + assertTrue(interiorSamples > 0); + } + + @Test + public void fractalConstructorsStartAtOneOctave() { + NoiseGenerator[] generators = { + new FractalCubicNoise(SEED), new FractalFBMSimplexNoise(SEED), + new FractalBillowPerlinNoise(SEED), new FractalBillowSimplexNoise(SEED), + new FractalRigidMultiSimplexNoise(SEED) + }; + for (NoiseGenerator generator : generators) { + double initial = generator.noise(37.219D, -91.713D, 17.731D); + ((OctaveNoise) generator).setOctaves(1); + assertEquals(initial, generator.noise(37.219D, -91.713D, 17.731D), 0D); + } + } + + private void assertBounded(double value) { + assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseBoundaryTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseBoundaryTest.java new file mode 100644 index 000000000..a86e20dc6 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseBoundaryTest.java @@ -0,0 +1,46 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CloverNoiseBoundaryTest { + @Test + public void threeDimensionalNoiseRemainsContinuousAcrossZeroZ() { + for (long seed : new long[]{0L, 1337L, Long.MIN_VALUE}) { + CloverNoise noise = new CloverNoise(seed); + for (double x = -3.125D; x < 4D; x += 0.375D) { + double atZero = noise.noise(x, 0.417D, 0D); + assertEquals(atZero, noise.noise(x, 0.417D, -1E-7D), 1E-5D); + assertEquals(atZero, noise.noise(x, 0.417D, 1E-7D), 1E-5D); + } + } + } + + @Test(timeout = 5000) + public void fractalIterationsCannotDivideByZeroOrRunUnbounded() { + CloverNoise.Noise2D twoDimensional = new CloverNoise.Noise2D(1337L); + CloverNoise.Noise3D threeDimensional = new CloverNoise.Noise3D(1337L); + assertEquals(twoDimensional.noise(0.25D, -0.75D), + twoDimensional.fractalNoise(0.25D, -0.75D, 0), 0D); + assertEquals(threeDimensional.noise(0.25D, 0.5D, -0.75D), + threeDimensional.fractalNoise(0.25D, 0.5D, -0.75D, Integer.MIN_VALUE), 0D); + double bounded2D = twoDimensional.fractalNoise(0.25D, -0.75D, 16); + double bounded3D = threeDimensional.fractalNoise(0.25D, 0.5D, -0.75D, 16); + assertEquals(bounded2D, twoDimensional.fractalNoise(0.25D, -0.75D, Integer.MAX_VALUE), 0D); + assertEquals(bounded3D, threeDimensional.fractalNoise(0.25D, 0.5D, -0.75D, Integer.MAX_VALUE), 0D); + assertTrue(Double.isFinite(bounded2D)); + assertTrue(Double.isFinite(bounded3D)); + } + @Test + public void stationaryCurlVectorsNormalizeToZero() { + CloverNoise.Vector2 twoDimensional = new CloverNoise.Vector2().normalize(); + CloverNoise.Vector3 threeDimensional = new CloverNoise.Vector3().normalize(); + assertEquals(0D, twoDimensional.getX(), 0D); + assertEquals(0D, twoDimensional.getY(), 0D); + assertEquals(0D, threeDimensional.getX(), 0D); + assertEquals(0D, threeDimensional.getY(), 0D); + assertEquals(0D, threeDimensional.getZ(), 0D); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseParityTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseParityTest.java index f7e1fda32..a5a9efbe4 100644 --- a/core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseParityTest.java +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CloverNoiseParityTest.java @@ -2,6 +2,13 @@ package art.arcane.iris.util.project.noise; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + import static org.junit.Assert.assertEquals; public class CloverNoiseParityTest { @@ -40,4 +47,59 @@ public class CloverNoiseParityTest { } } } + + @Test + public void threeDimensionalCoordinateGridPreservesExactNoiseBits() { + long[] expected = {-4534805072329300766L, 938556028071277557L, -6813256383186365522L, + -4452859942568220233L, 5365194784097108235L}; + + for (int index = 0; index < SEEDS.length; index++) { + assertEquals("seed=" + SEEDS[index], expected[index], coordinateDigest(new CloverNoise(SEEDS[index]), 0)); + } + } + + @Test(timeout = 15000) + public void concurrentSamplingKeepsThreadScratchIndependent() throws Exception { + CloverNoise shared = new CloverNoise(1337L); + long[] expected = new long[8]; + for (int index = 0; index < expected.length; index++) { + expected[index] = coordinateDigest(shared, index * 991); + } + + try (ExecutorService executor = Executors.newFixedThreadPool(4)) { + List> results = new ArrayList<>(expected.length); + for (int index = 0; index < expected.length; index++) { + int start = index * 991; + results.add(executor.submit(() -> coordinateDigest(shared, start))); + } + + for (int index = 0; index < expected.length; index++) { + assertEquals(expected[index], results.get(index).get(5, TimeUnit.SECONDS).longValue()); + } + } + } + + @Test + public void samplingKeepsCallerVectorsUnchanged() { + CloverNoise.Noise3D noise = new CloverNoise.Noise3D(1337L); + CloverNoise.Vector3 point = new CloverNoise.Vector3(0.25D, 0.5D, -0.75D); + double expected = noise.noise(point); + noise.noise(71D, -31D, 113D); + + assertEquals(0.25D, point.getX(), 0D); + assertEquals(0.5D, point.getY(), 0D); + assertEquals(-0.75D, point.getZ(), 0D); + assertEquals(expected, noise.noise(point), 0D); + } + + private static long coordinateDigest(CloverNoise noise, int start) { + long digest = 0L; + for (int index = start; index < start + 8192; index++) { + double x = ((index * 1580030173L) % 60000000L - 30000000L) / 64D + 0.371D; + double y = ((index * 91437L) % 384L - 64L) / 64D + 0.231D; + double z = ((index * 59260789L) % 60000000L - 30000000L) / 64D - 0.219D; + digest = Long.rotateLeft(digest, 7) ^ Double.doubleToRawLongBits(noise.noise(x, y, z)); + } + return digest; + } } 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 index 4dc7ebc7c..cbb51de77 100644 --- 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 @@ -6,6 +6,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class FastNoiseDoubleCellularParityTest { private static final FastNoiseDouble.CellularDistanceFunction[] DISTANCE_FUNCTIONS = new FastNoiseDouble.CellularDistanceFunction[]{ @@ -29,7 +30,7 @@ public class FastNoiseDoubleCellularParityTest { private static final double[] CELL_3D_Z = getDeclaredDoubleArray("CELL_3D_Z"); @Test - public void cellular2DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception { + public void cellular2DTwoEdgeModesMatchExpandedReference() throws Exception { for (long seed = 11L; seed <= 17L; seed++) { double frequency = 0.037D + (seed * 0.001D); for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) { @@ -42,7 +43,7 @@ public class FastNoiseDoubleCellularParityTest { 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 expected = referenceGetCellular2D(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); } @@ -53,7 +54,7 @@ public class FastNoiseDoubleCellularParityTest { } @Test - public void cellular3DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception { + public void cellular3DTwoEdgeModesMatchExpandedReference() throws Exception { for (long seed = 31L; seed <= 35L; seed++) { double frequency = 0.029D + (seed * 0.001D); for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) { @@ -68,7 +69,7 @@ public class FastNoiseDoubleCellularParityTest { 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 expected = referenceGetCellular3D(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); } @@ -79,7 +80,44 @@ public class FastNoiseDoubleCellularParityTest { } } - private double legacyGetCellular2D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y) throws Exception { + @Test + public void secondNearestCellMayLieOutsideImmediateNeighbors() throws Exception { + long seed2D = 2320148477032134941L; + long seed3D = 8394744594210714849L; + for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) { + for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) { + FastNoiseDouble noise = new FastNoiseDouble(seed2D); + noise.setFrequency(1D); + noise.setCellularDistanceFunction(distanceFunction); + noise.setCellularReturnType(returnType); + double x = 783.44443982D; + double y = 1896.50036984D; + assertEquals(referenceGetCellular2D(seed2D, 1D, distanceFunction, returnType, x, y), + noise.GetCellular(x, y), 0D); + noise.setSeed(seed3D); + x = -2741.75935805D; + y = -1049.4161945D; + double z = -1528.00711843D; + assertEquals(referenceGetCellular3D(seed3D, 1D, distanceFunction, returnType, x, y, z), + noise.GetCellular(x, y, z), 0D); + } + } + } + + @Test + public void distanceProfilesStayContinuousWhenSearchCenterChanges() { + FastNoiseDouble noise = new FastNoiseDouble(2320148477032134941L); + noise.setFrequency(1D); + noise.setCellularDistanceFunction(FastNoiseDouble.CellularDistanceFunction.Natural); + for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) { + noise.setCellularReturnType(returnType); + double below = noise.GetCellular(783.44443982D, 1896.5D - 1E-8D); + double above = noise.GetCellular(783.44443982D, 1896.5D + 1E-8D); + assertTrue(Math.abs(above - below) < 1E-6D); + } + } + + private double referenceGetCellular2D(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); @@ -87,8 +125,8 @@ public class FastNoiseDoubleCellularParityTest { 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 xi = xr - 2; xi <= xr + 2; xi++) { + for (long yi = yr - 2; yi <= yr + 2; 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]; @@ -105,7 +143,7 @@ public class FastNoiseDoubleCellularParityTest { 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 { + private double referenceGetCellular3D(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; @@ -114,10 +152,11 @@ public class FastNoiseDoubleCellularParityTest { long zr = fastRound(scaledZ); double distance = 999999D; double distance2 = 999999D; + int radius = distanceFunction == FastNoiseDouble.CellularDistanceFunction.Manhattan ? 3 : 2; - 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++) { + for (long xi = xr - radius; xi <= xr + radius; xi++) { + for (long yi = yr - radius; yi <= yr + radius; yi++) { + for (long zi = zr - radius; zi <= zr + radius; 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]; diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/FastNoiseFloorTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/FastNoiseFloorTest.java new file mode 100644 index 000000000..2edca5a40 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/FastNoiseFloorTest.java @@ -0,0 +1,33 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import java.lang.reflect.Method; + +import static org.junit.Assert.assertEquals; + +public class FastNoiseFloorTest { + @Test + public void doubleLatticeFloorHandlesNegativeIntegersAndNeighbors() throws Exception { + Method floor = FastNoiseDouble.class.getDeclaredMethod("fastFloor", double.class); + floor.setAccessible(true); + assertEquals(-1L, ((Long) floor.invoke(null, -1D)).longValue()); + assertEquals(-2L, ((Long) floor.invoke(null, -2D)).longValue()); + assertEquals(-2L, ((Long) floor.invoke(null, Math.nextDown(-1D))).longValue()); + assertEquals(-1L, ((Long) floor.invoke(null, Math.nextUp(-1D))).longValue()); + assertEquals(0L, ((Long) floor.invoke(null, -0D)).longValue()); + assertEquals(Long.MIN_VALUE, ((Long) floor.invoke(null, (double) Long.MIN_VALUE)).longValue()); + } + + @Test + public void floatLatticeFloorHandlesNegativeIntegersAndNeighbors() throws Exception { + Method floor = FastNoise.class.getDeclaredMethod("FastFloor", float.class); + floor.setAccessible(true); + assertEquals(-1, ((Integer) floor.invoke(null, -1F)).intValue()); + assertEquals(-2, ((Integer) floor.invoke(null, -2F)).intValue()); + assertEquals(-2, ((Integer) floor.invoke(null, Math.nextDown(-1F))).intValue()); + assertEquals(-1, ((Integer) floor.invoke(null, Math.nextUp(-1F))).intValue()); + assertEquals(0, ((Integer) floor.invoke(null, -0F)).intValue()); + assertEquals(Integer.MIN_VALUE, ((Integer) floor.invoke(null, (float) Integer.MIN_VALUE)).intValue()); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/GeometricNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/GeometricNoiseTest.java new file mode 100644 index 000000000..1e76a96ef --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/GeometricNoiseTest.java @@ -0,0 +1,156 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class GeometricNoiseTest { + private static final double SQRT_3 = Math.sqrt(3D); + private static final long[] SEEDS = {0L, -1L, 1337L, Long.MIN_VALUE, Long.MAX_VALUE}; + + @Test + public void regularHexagonsMatchNearestLatticeCenters() { + NoiseGenerator[] noises = {new HexagonNoise(177L), new HexSimplexNoise(311L)}; + for (NoiseGenerator noise : noises) { + for (double x = -5.93D; x < 6D; x += 0.137D) { + for (double z = -5.97D; z < 6D; z += 0.173D) { + double bestDistance = Double.POSITIVE_INFINITY; + double centerX = 0D; + double centerZ = 0D; + for (int q = -8; q <= 8; q++) { + for (int r = -8; r <= 8; r++) { + double candidateX = SQRT_3 * (q + (r * 0.5D)); + double candidateZ = 1.5D * r; + double dx = x - candidateX; + double dz = z - candidateZ; + double distance = (dx * dx) + (dz * dz); + if (distance < bestDistance) { + bestDistance = distance; + centerX = candidateX; + centerZ = candidateZ; + } + } + } + assertEquals(noise.noise(centerX, centerZ), noise.noise(x, z), 0D); + assertEquals(noise.noise(centerX, 2.75D, centerZ), noise.noise(x, 2.75D, z), 0D); + } + } + } + } + + @Test + public void hexagonNeighborsFollowCoherentHeat() { + HexagonNoise noise = new HexagonNoise(7331L); + double adjacentDifference = 0D; + double distantDifference = 0D; + for (int q = -32; q <= 32; q++) { + for (int r = -32; r <= 32; r++) { + double x = SQRT_3 * (q + (r * 0.5D)); + double z = 1.5D * r; + double sample = noise.noise(x, z); + adjacentDifference += Math.abs(sample - noise.noise(x + SQRT_3, z)); + distantDifference += Math.abs(sample - noise.noise(x + (SQRT_3 * 19D), z)); + } + } + assertTrue(adjacentDifference < distantDifference * 0.85D); + } + + @Test + public void nestedHexagonsHaveRegularInscribedFootprints() { + for (int child = 0; child < 7; child++) { + double angle = Math.toRadians((child * 60D) - 90D); + double centerX = child == 0 ? 0D : Math.cos(angle) * (2D / 3D); + double centerZ = child == 0 ? 0D : Math.sin(angle) * (2D / 3D); + for (int direction = 0; direction < 60; direction++) { + double sampleAngle = Math.toRadians(direction * 6D); + double x = centerX + (Math.cos(sampleAngle) * 0.27D); + double z = centerZ + (Math.sin(sampleAngle) * 0.27D); + double q = (x / SQRT_3) - (z / 3D); + double r = 2D * z / 3D; + assertEquals(child, HexagonalNoise.pickChildIndex(q, r)); + assertEquals(0L, HexagonalNoise.roundQ(q, r)); + assertEquals(0L, HexagonalNoise.roundR(q, r)); + } + } + } + + @Test + public void sierpinskiCutsTriangleCentroidsAtEveryDepth() { + SierpinskiTriangleNoise noise = new SierpinskiTriangleNoise(91L); + for (int depth = 0; depth < 4; depth++) { + double size = Math.scalb(1D, -depth); + double holeU = size / 3D; + double holeV = size / 3D; + assertTrue(sampleTriangle(noise, holeU, holeV) <= 0.12D); + assertTrue(sampleTriangle(noise, 1D - size + holeU, holeV) <= 0.12D); + assertTrue(sampleTriangle(noise, holeU, 1D - size + holeV) <= 0.12D); + } + assertTrue(sampleTriangle(noise, 1D / 48D, 1D / 48D) >= 0.35D); + assertTrue(sampleTriangle(noise, 46D / 48D, 1D / 48D) >= 0.35D); + assertTrue(sampleTriangle(noise, 1D / 48D, 46D / 48D) >= 0.35D); + } + + @Test + public void sierpinskiTilesAcrossNegativeCoordinatesWithExpectedArea() { + SierpinskiTriangleNoise noise = new SierpinskiTriangleNoise(9123L); + Random random = new Random(8211L); + int filled = 0; + int samples = 65536; + for (int index = 0; index < samples; index++) { + double u = random.nextDouble(); + double v = random.nextDouble(); + double x = 8D * (u + (v * 0.5D)); + double z = 4D * SQRT_3 * v; + boolean inside = noise.noise(x, z) >= 0.35D; + if (inside) { + filled++; + } + assertEquals(inside, noise.noise(x - 8D, z) >= 0.35D); + assertEquals(inside, noise.noise(x - 4D, z - (4D * SQRT_3)) >= 0.35D); + } + assertEquals(Math.pow(0.75D, 4), filled / (double) samples, 0.006D); + } + + @Test + public void dimensionalSlicesAreConsistentAndContinuous() { + for (NoiseGenerator noise : generators(1337L)) { + assertEquals(noise.noise(3.125D), noise.noise(3.125D, 0D), 0D); + assertEquals(noise.noise(3.125D, -1.75D), noise.noise(3.125D, 0D, -1.75D), 0D); + assertEquals(noise.noise(3.125D, 1.75D, 0D), noise.noise(3.125D, 1.75D, 1E-9D), 1E-7D); + assertEquals(noise.noise(3.125D, 0D, -1.75D), noise.noise(3.125D, 1E-9D, -1.75D), 1E-7D); + } + } + + @Test + public void seedsAndWorldBorderCoordinatesRemainDeterministicAndBounded() { + double[][] points = {{0D, 0D, 0D}, {-29_999_984D, -64D, 29_999_984D}, + {3.125D, 17.375D, -9.25D}, {29_999_984D, 319D, -29_999_984D}}; + for (long seed : SEEDS) { + NoiseGenerator[] first = generators(seed); + NoiseGenerator[] second = generators(seed); + for (int index = 0; index < first.length; index++) { + if (first[index] instanceof OctaveNoise octaves) { + octaves.setOctaves(16); + ((OctaveNoise) second[index]).setOctaves(16); + } + for (double[] point : points) { + double value = first[index].noise(point[0], point[1], point[2]); + assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D); + assertEquals(value, second[index].noise(point[0], point[1], point[2]), 0D); + } + } + } + } + + private double sampleTriangle(SierpinskiTriangleNoise noise, double u, double v) { + return noise.noise(8D * (u + (v * 0.5D)), 4D * SQRT_3 * v); + } + + private NoiseGenerator[] generators(long seed) { + return new NoiseGenerator[]{new HexagonNoise(seed), new HexSimplexNoise(seed), + new HexRandomSizeNoise(seed), new HexJamesNoise(seed), new SierpinskiTriangleNoise(seed)}; + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/InterpolatedNoiseDimensionTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/InterpolatedNoiseDimensionTest.java new file mode 100644 index 000000000..b9653c2c8 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/InterpolatedNoiseDimensionTest.java @@ -0,0 +1,87 @@ +package art.arcane.iris.util.project.noise; + +import art.arcane.iris.util.project.interpolation.InterpolationMethod; +import art.arcane.iris.util.project.interpolation.IrisInterpolation; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class InterpolatedNoiseDimensionTest { + @Test + public void threeDimensionalSamplesAlwaysUseTheHorizontalPlane() { + InterpolatedNoise noise = new InterpolatedNoise(37L, NoiseType.WHITE, InterpolationMethod.BILINEAR); + + for (double x : new double[]{-13D, 0D, 41D}) { + for (double y : new double[]{-89D, 0D, 173D}) { + for (double z : new double[]{-37D, 0D, 29D}) { + assertEquals(noise.noise(x, z), noise.noise(x, y, z), 0D); + } + } + assertEquals(noise.noise(x, 0D), noise.noise(x), 0D); + } + } + + @Test + public void interpolationSamplesTheNormalizedSourceAndForwardsOctaves() { + long seed = 93L; + NoiseType type = NoiseType.SIMPLEX; + NoiseGenerator source = type.create(seed); + ((OctaveNoise) source).setOctaves(4); + InterpolatedNoise noise = new InterpolatedNoise(seed, type, InterpolationMethod.BILINEAR); + noise.setOctaves(4); + double scale = type.getCoordinateScale(); + + for (int x = -49; x <= 49; x += 7) { + for (int z = -51; z <= 51; z += 11) { + double expected = IrisInterpolation.getNoise(InterpolationMethod.BILINEAR, x, z, 32D, + (sampleX, sampleZ) -> source.noise(sampleX * scale, sampleZ * scale)); + assertEquals(expected, noise.noise(x, z), 1.0E-12D); + } + } + } + + @Test + public void fractionalCoordinatesReachEveryInterpolationKernel() { + long seed = 109L; + NoiseType type = NoiseType.SIMPLEX; + NoiseGenerator source = type.create(seed); + double scale = type.getCoordinateScale(); + + for (InterpolationMethod method : InterpolationMethod.values()) { + InterpolatedNoise noise = new InterpolatedNoise(seed, type, method); + double expected = IrisInterpolation.getNoise(method, -7.75D, 3.25D, 32D, + (x, z) -> source.noise(x * scale, z * scale)); + double actual = noise.noise(-7.75D, 3.25D); + + assertEquals(method.name(), expected, actual, 0D); + assertNotEquals(method.name(), noise.noise(-7D, 3D), actual, 1.0E-9D); + } + } + + @Test + public void noiseWrappersConstrainCubicOvershootWithoutConstrainingTheKernel() { + double overshoot = IrisInterpolation.getNoise(InterpolationMethod.BICUBIC, 16D, 16D, 32D, + (x, z) -> x >= 0D && x <= 32D ? 1D : 0D); + assertEquals(1.25D, overshoot, 0D); + + for (NoiseType type : new NoiseType[]{NoiseType.WHITE, NoiseType.CELLULAR, NoiseType.CLOVER}) { + for (InterpolationMethod method : new InterpolationMethod[]{InterpolationMethod.BICUBIC, + InterpolationMethod.HERMITE}) { + InterpolatedNoise noise = new InterpolatedNoise(173L, type, method); + boolean reachesEndpoint = false; + for (double x = -256D; x <= 256D; x += 7.375D) { + for (double z = -256D; z <= 256D; z += 5.5D) { + double value = noise.noise(x, z); + assertTrue(type.name() + "/" + method.name(), value >= 0D && value <= 1D); + reachesEndpoint |= value == 0D || value == 1D; + } + } + if (type == NoiseType.WHITE) { + assertTrue(method.name(), reachesEndpoint); + } + } + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/NoiseOctaveTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/NoiseOctaveTest.java new file mode 100644 index 000000000..9aa50cf59 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/NoiseOctaveTest.java @@ -0,0 +1,104 @@ +package art.arcane.iris.util.project.noise; + +import art.arcane.volmlib.util.math.RNG; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class NoiseOctaveTest { + private static final long SEED = 972453L; + private static final double X = 37.219D; + private static final double Y = -91.713D; + private static final double Z = 152.875D; + + @Test + public void simplexAndPerlinKeepFundamentalAndReduceDetailAmplitude() { + for (boolean simplex : new boolean[]{false, true}) { + NoiseGenerator noise = simplex ? new SimplexNoise(SEED) : new PerlinNoise(SEED); + ((OctaveNoise) noise).setOctaves(3); + FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax()); + source.setLongerp(FastNoiseDouble.Longerp.Qulongic); + for (int dimensions = 1; dimensions <= 3; dimensions++) { + double expected = 0D; + for (int octave = 0; octave < 3; octave++) { + double frequency = 1 << octave; + double sample; + if (dimensions == 3) { + sample = simplex ? source.GetSimplex(X * frequency, Y * frequency, Z * frequency) + : source.GetPerlin(X * frequency, Y * frequency, Z * frequency); + } else { + double second = dimensions == 1 ? 0D : Z * frequency; + sample = simplex ? source.GetSimplex(X * frequency, second) + : source.GetPerlin(X * frequency, second); + } + expected += sample / frequency; + } + expected /= 1.75D; + double signed = switch (dimensions) { + case 1 -> noise.noiseSigned(X); + case 2 -> noise.noiseSigned(X, Z); + default -> noise.noiseSigned(X, Y, Z); + }; + double unsigned = switch (dimensions) { + case 1 -> noise.noise(X); + case 2 -> noise.noise(X, Z); + default -> noise.noise(X, Y, Z); + }; + assertEquals(expected, signed, 1E-15D); + assertEquals(signed / 2D + 0.5D, unsigned, 0D); + } + assertEquals(noise.noise(X, 0D), noise.noise(X), 0D); + } + } + + @Test(timeout = 5000) + public void octaveWrappersClampInvalidAndExcessiveCounts() { + NoiseGenerator[] generators = { + new SimplexNoise(SEED), new PerlinNoise(SEED), new FractalFBMSimplexNoise(SEED), + new FractalBillowSimplexNoise(SEED), new FractalBillowPerlinNoise(SEED), + new FractalRigidMultiSimplexNoise(SEED), new FractalCubicNoise(SEED) + }; + for (NoiseGenerator noise : generators) { + OctaveNoise octaveNoise = (OctaveNoise) noise; + octaveNoise.setOctaves(1); + double minimum = noise.noise(X, Y, Z); + octaveNoise.setOctaves(Integer.MIN_VALUE); + assertEquals(minimum, noise.noise(X, Y, Z), 0D); + octaveNoise.setOctaves(16); + double maximum = noise.noise(X, Y, Z); + octaveNoise.setOctaves(Integer.MAX_VALUE); + assertEquals(maximum, noise.noise(X, Y, Z), 0D); + assertTrue(Double.isFinite(maximum)); + } + } + + @Test + public void cubicFractalUsesRequestedOctaves() { + FractalCubicNoise noise = new FractalCubicNoise(SEED); + noise.setOctaves(1); + double fundamental = noise.noise(X, Y, Z); + noise.setOctaves(4); + double detailed = noise.noise(X, Y, Z); + assertNotEquals(fundamental, detailed, 1E-8D); + FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax()); + source.setFractalType(FastNoiseDouble.FractalType.Billow); + source.setFractalOctaves(4); + assertEquals(source.GetCubicFractal(X, Y, Z) / 2D + 0.5D, detailed, 0D); + } + + @Test(timeout = 5000) + public void rawFractalSamplersBoundOctaveWork() { + FastNoiseDouble doubles = new FastNoiseDouble(SEED); + FastNoise floats = new FastNoise((int) SEED); + doubles.setFractalOctaves(16); + floats.SetFractalOctaves(16); + double expectedDouble = doubles.GetSimplexFractal(X, Y, Z); + float expectedFloat = floats.GetSimplexFractal((float) X, (float) Y, (float) Z); + doubles.setFractalOctaves(Long.MAX_VALUE); + floats.SetFractalOctaves(Integer.MAX_VALUE); + assertEquals(expectedDouble, doubles.GetSimplexFractal(X, Y, Z), 0D); + assertEquals(expectedFloat, floats.GetSimplexFractal((float) X, (float) Y, (float) Z), 0D); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/WhiteNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/WhiteNoiseTest.java new file mode 100644 index 000000000..d09bc7bc4 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/WhiteNoiseTest.java @@ -0,0 +1,40 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class WhiteNoiseTest { + @Test + public void scatterDoesNotRepeatEvery8192Blocks() { + WhiteNoise noise = new WhiteNoise(7153L); + for (double x : new double[]{-29999999.25D, -8192D, 0D, 37.5D, 8192D}) { + assertNotEquals(noise.noise(x, 73D), noise.noise(x + 8192D, 73D), 0D); + assertNotEquals(noise.noise(x, 53D, 73D), noise.noise(x, 53D, 73D + 8192D), 0D); + } + } + + @Test + public void fractionalCoordinatesRemainDistinctNearWorldBorder() { + WhiteNoise noise = new WhiteNoise(15273L); + double x = 29999111.25D; + assertNotEquals(noise.noise(x, -x), noise.noise(x + 0.0001D, -x), 0D); + assertNotEquals(noise.noise(x, 71.5D, -x), noise.noise(x, 71.5001D, -x), 0D); + } + + @Test + public void scatterIsRepeatableAndUsesFullSeed() { + WhiteNoise first = new WhiteNoise(725L); + WhiteNoise same = new WhiteNoise(725L); + WhiteNoise upperBits = new WhiteNoise(725L + (1L << 48)); + for (int index = 0; index < 512; index++) { + double x = index * 1.25D; + double value = first.noise(x, 53D, -x); + assertEquals(value, same.noise(x, 53D, -x), 0D); + assertTrue(value >= 0D && value <= 1D); + assertNotEquals(value, upperBits.noise(x, 53D, -x), 0D); + } + } +}