From 50894b90ce97aab611d20881d0ca04f24171c3f2 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sat, 5 Sep 2026 19:32:13 -0400 Subject: [PATCH 1/3] 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); + } + } +} From 645edcff5c7a6ae4339a05efc3fcf3e2b881e877 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sat, 5 Sep 2026 19:33:14 -0400 Subject: [PATCH 2/3] Added patterned noise - Gyroid and Quasicrystal provide labyrinths and wave interference - Truchet tiles join quarter-circle ribbons - Crater and Vortex provide bowls and spiral fields - Pattern samplers share bounded octave accumulation - Tests cover seeds, geometry, volume sampling, and world edges --- .../arcane/iris/engine/object/NoiseStyle.java | 15 +++ .../iris/util/project/noise/CraterNoise.java | 62 ++++++++++ .../iris/util/project/noise/GyroidNoise.java | 44 +++++++ .../iris/util/project/noise/NoiseType.java | 7 +- .../iris/util/project/noise/PatternNoise.java | 40 +++++++ .../util/project/noise/QuasicrystalNoise.java | 37 ++++++ .../iris/util/project/noise/TruchetNoise.java | 49 ++++++++ .../iris/util/project/noise/VortexNoise.java | 63 ++++++++++ .../util/project/noise/CraterNoiseTest.java | 82 +++++++++++++ .../util/project/noise/PatternNoiseTest.java | 110 ++++++++++++++++++ .../util/project/noise/TruchetNoiseTest.java | 70 +++++++++++ .../util/project/noise/VortexNoiseTest.java | 37 ++++++ .../project/noise/WavePatternNoiseTest.java | 85 ++++++++++++++ 13 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/CraterNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/GyroidNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/PatternNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/QuasicrystalNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/TruchetNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/VortexNoise.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/CraterNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/TruchetNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/VortexNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/WavePatternNoiseTest.java 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 fd339091c..3febe384a 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 @@ -538,6 +538,21 @@ public enum NoiseStyle { @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), NoiseType.VASCULAR), + + @Desc("Warped gyroid sheets form rounded mazes in 2D and interconnected labyrinths in 3D.") + GYROID(rng -> new CNG(rng, NoiseType.GYROID, 1D, 1), NoiseType.GYROID), + + @Desc("Fivefold wave interference forms non-repeating stars, rosettes, and crystalline contours.") + QUASICRYSTAL(rng -> new CNG(rng, NoiseType.QUASICRYSTAL, 1D, 1), NoiseType.QUASICRYSTAL), + + @Desc("Connected quarter-circle ribbons form tiled loops that twist continuously through height.") + TRUCHET(rng -> new CNG(rng, NoiseType.TRUCHET, 1D, 1), NoiseType.TRUCHET), + + @Desc("Scattered bowls with raised rims form crater fields in 2D and hollow shells in 3D.") + CRATER(rng -> new CNG(rng, NoiseType.CRATER, 1D, 1), NoiseType.CRATER), + + @Desc("Seeded spiral arms form swirling eddies in 2D and winding funnels in 3D.") + VORTEX(rng -> new CNG(rng, NoiseType.VORTEX, 1D, 1), NoiseType.VORTEX), ; private final CNGFactory f; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CraterNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/CraterNoise.java new file mode 100644 index 000000000..e358f08e7 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CraterNoise.java @@ -0,0 +1,62 @@ +package art.arcane.iris.util.project.noise; + +public final class CraterNoise extends PatternNoise { + private static final double CENTER_SPREAD = 0.6D / 65535D; + private static final double MAX_SUPPORT_SQUARED = 0.6D * 0.6D * 1.35D; + + private final long seed; + + public CraterNoise(long seed) { + this.seed = seed; + } + + @Override + protected double sample(double x, double y, double z) { + long cellX = (long) Math.floor(x); + long cellY = (long) Math.floor(y); + long cellZ = (long) Math.floor(z); + double bowl = 0D; + double rim = 0D; + + for (int offsetY = -1; offsetY <= 1; offsetY++) { + long pointY = cellY + offsetY; + long hashY = pointY * 0xC2B2AE3D27D4EB4FL; + for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { + long pointZ = cellZ + offsetZ; + long hashYZ = hashY ^ pointZ * 0x165667B19E3779F9L; + for (int offsetX = -1; offsetX <= 1; offsetX++) { + long pointX = cellX + offsetX; + long hash = mix(seed ^ pointX * 0x9E3779B97F4A7C15L ^ hashYZ); + double deltaX = x - (pointX + 0.2D + (hash & 65535L) * CENTER_SPREAD); + double deltaY = y - (pointY + 0.2D + ((hash >>> 16) & 65535L) * CENTER_SPREAD); + double deltaZ = z - (pointZ + 0.2D + ((hash >>> 32) & 65535L) * CENTER_SPREAD); + double distanceSquared = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ; + if (distanceSquared >= MAX_SUPPORT_SQUARED) { + continue; + } + + double radius = 0.36D + (hash >>> 56) * (0.24D / 255D); + double relativeDistance = distanceSquared / (radius * radius); + if (relativeDistance >= 1.35D) { + continue; + } + + double depth = ((hash >>> 48) & 255L) / 255D; + double depression = Math.max(0D, 1D - relativeDistance); + bowl = Math.min(bowl, -(0.28D + depth * 0.17D) * depression * depression); + double rimDistance = (relativeDistance - 1D) / 0.35D; + double ridge = Math.max(0D, 1D - rimDistance * rimDistance); + rim = Math.max(rim, (0.18D + (1D - depth) * 0.12D) * ridge * ridge); + } + } + } + + return 0.5D + bowl + rim; + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/GyroidNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/GyroidNoise.java new file mode 100644 index 000000000..5f7682e8e --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/GyroidNoise.java @@ -0,0 +1,44 @@ +package art.arcane.iris.util.project.noise; + +import java.util.SplittableRandom; + +public final class GyroidNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + + private final double horizontalCosine; + private final double horizontalSine; + private final double tiltCosine; + private final double tiltSine; + private final double phaseX; + private final double phaseY; + private final double phaseZ; + + public GyroidNoise(long seed) { + SplittableRandom random = new SplittableRandom(seed); + double horizontalAngle = random.nextDouble(TAU); + double tiltAngle = random.nextDouble(0.35D, 1.2D); + horizontalCosine = Math.cos(horizontalAngle); + horizontalSine = Math.sin(horizontalAngle); + tiltCosine = Math.cos(tiltAngle); + tiltSine = Math.sin(tiltAngle); + phaseX = random.nextDouble(TAU); + phaseY = random.nextDouble(TAU); + phaseZ = random.nextDouble(TAU); + } + + @Override + protected double sample(double x, double y, double z) { + double horizontal = x * horizontalCosine + z * horizontalSine; + double transverse = z * horizontalCosine - x * horizontalSine; + double u = TAU * (horizontal * tiltCosine + y * tiltSine) + phaseX; + double v = TAU * (y * tiltCosine - horizontal * tiltSine) + phaseY; + double w = TAU * transverse + phaseZ; + double a = u + 0.7D * Math.sin(0.37D * v + 0.29D * w + phaseZ); + double b = v + 0.7D * Math.sin(0.31D * w - 0.23D * u + phaseX); + double c = w + 0.7D * Math.sin(0.41D * u + 0.19D * v + phaseY); + double field = Math.sin(a) * Math.cos(b) + Math.sin(b) * Math.cos(c) + Math.sin(c) * Math.cos(a); + double width = 0.62D + 0.22D * Math.sin(0.29D * u + 0.23D * v - 0.19D * w); + double sheet = Math.max(0D, 1D - Math.abs(field) / width); + return sheet * sheet * (3D - 2D * sheet); + } +} 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 0dbd95513..78c6e0079 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 @@ -87,7 +87,12 @@ public enum NoiseType { 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); + VASCULAR(VascularNoise::new), + GYROID(GyroidNoise::new, 1D / 64D), + QUASICRYSTAL(QuasicrystalNoise::new, 1D / 64D), + TRUCHET(TruchetNoise::new, 1D / 64D), + CRATER(CraterNoise::new, 1D / 64D), + VORTEX(VortexNoise::new, 1D / 64D); private final NoiseFactory f; private final double coordinateScale; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/PatternNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/PatternNoise.java new file mode 100644 index 000000000..90921892a --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/PatternNoise.java @@ -0,0 +1,40 @@ +package art.arcane.iris.util.project.noise; + +abstract class PatternNoise implements NoiseGenerator, OctaveNoise { + private int octaves = 1; + private double bounding = 1D; + + @Override + public final double noise(double x) { + return noise(x, 0D); + } + + @Override + public final double noise(double x, double z) { + return noise(x, 0D, z); + } + + @Override + public final double noise(double x, double y, double z) { + if (octaves == 1) { + return sample(x, y, z); + } + double sum = 0D; + double frequency = 1D; + double amplitude = 1D; + for (int octave = 0; octave < octaves; octave++) { + sum += sample(x * frequency, y * frequency, z * frequency) * amplitude; + frequency *= 2D; + amplitude *= 0.5D; + } + return sum * bounding; + } + + @Override + public final void setOctaves(int octaves) { + this.octaves = Math.clamp(octaves, 1, 16); + bounding = 1D / (2D - Math.scalb(1D, 1 - this.octaves)); + } + + protected abstract double sample(double x, double y, double z); +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/QuasicrystalNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/QuasicrystalNoise.java new file mode 100644 index 000000000..1c78abd96 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/QuasicrystalNoise.java @@ -0,0 +1,37 @@ +package art.arcane.iris.util.project.noise; + +import java.util.SplittableRandom; + +public final class QuasicrystalNoise extends PatternNoise { + private static final int WAVES = 5; + private static final double TAU = Math.PI * 2D; + + private final double[] waveX = new double[WAVES]; + private final double[] waveZ = new double[WAVES]; + private final double phase; + private final double verticalFrequency; + private final double contourPhase; + + public QuasicrystalNoise(long seed) { + SplittableRandom random = new SplittableRandom(seed); + double rotation = random.nextDouble(TAU); + for (int i = 0; i < WAVES; i++) { + double angle = rotation + i * TAU / WAVES; + waveX[i] = Math.cos(angle) * TAU; + waveZ[i] = Math.sin(angle) * TAU; + } + phase = random.nextDouble(TAU); + verticalFrequency = random.nextDouble(0.3D, 0.7D) * TAU; + contourPhase = random.nextDouble(TAU); + } + + @Override + protected double sample(double x, double y, double z) { + double layerPhase = phase + y * verticalFrequency; + double sum = 0D; + for (int i = 0; i < WAVES; i++) { + sum += Math.cos(x * waveX[i] + z * waveZ[i] + layerPhase); + } + return 0.5D + 0.5D * Math.sin(sum * 1.4D + contourPhase); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/TruchetNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/TruchetNoise.java new file mode 100644 index 000000000..2c4fb2913 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/TruchetNoise.java @@ -0,0 +1,49 @@ +package art.arcane.iris.util.project.noise; + +public final class TruchetNoise extends PatternNoise { + private static final double RIBBON_WIDTH = 0.075D; + private static final double HEIGHT_FREQUENCY = Math.PI * 0.25D; + + private final long seed; + private final double phaseX; + private final double phaseZ; + + public TruchetNoise(long seed) { + this.seed = seed; + phaseX = 0.17D + (mix(seed) >>> 11) * 0x1.0p-53D * 0.61D; + phaseZ = 0.13D + (mix(seed + 0x9E3779B97F4A7C15L) >>> 11) * 0x1.0p-53D * 0.73D; + } + + @Override + protected double sample(double x, double y, double z) { + x += phaseX; + z += phaseZ; + if (y != 0D) { + double angle = y * HEIGHT_FREQUENCY; + x += Math.sin(angle) * 0.3D + y * 0.125D; + z += (Math.cos(angle) - 1D) * 0.3D; + } + + long cellX = (long) Math.floor(x); + long cellZ = (long) Math.floor(z); + double localX = x - cellX; + double localZ = z - cellZ; + long orientation = mix(seed ^ cellX * 0x9E3779B97F4A7C15L ^ cellZ * 0xC2B2AE3D27D4EB4FL); + if ((orientation & 1L) != 0L) { + localX = 1D - localX; + } + + double oppositeX = 1D - localX; + double oppositeZ = 1D - localZ; + double first = Math.abs(Math.sqrt(localX * localX + localZ * localZ) - 0.5D); + double second = Math.abs(Math.sqrt(oppositeX * oppositeX + oppositeZ * oppositeZ) - 0.5D); + double ribbon = Math.max(0D, 1D - Math.min(first, second) / RIBBON_WIDTH); + return ribbon * ribbon * (3D - 2D * ribbon); + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/VortexNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/VortexNoise.java new file mode 100644 index 000000000..c3e21ace3 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/VortexNoise.java @@ -0,0 +1,63 @@ +package art.arcane.iris.util.project.noise; + +public final class VortexNoise extends PatternNoise { + private static final double RADIUS = 0.9D; + private static final double RADIUS_SQUARED = RADIUS * RADIUS; + private static final double TAU = Math.PI * 2D; + + private final long seed; + + public VortexNoise(long seed) { + this.seed = seed; + } + + @Override + protected double sample(double x, double y, double z) { + long cellX = (long) Math.floor(x); + long cellZ = (long) Math.floor(z); + double sum = 0D; + double weightSum = 0.07D; + for (int offsetX = -1; offsetX <= 1; offsetX++) { + long gridX = cellX + offsetX; + for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { + long gridZ = cellZ + offsetZ; + long hash = hash(gridX, gridZ); + double dx = x - (gridX + 0.2D + unit(hash) * 0.6D); + hash = mix(hash); + double dz = z - (gridZ + 0.2D + unit(hash) * 0.6D); + double radiusSquared = dx * dx + dz * dz; + if (radiusSquared >= RADIUS_SQUARED) { + continue; + } + double radius = Math.sqrt(radiusSquared); + double envelope = 1D - radiusSquared / RADIUS_SQUARED; + double weight = envelope * envelope; + hash = mix(hash); + int arms = 2 + (int) (hash & 1L); + int direction = (hash & 2L) == 0L ? -1 : 1; + double phase = TAU * unit(hash); + hash = mix(hash); + double winding = 12D + unit(hash) * 4D; + double wave = Math.sin(direction * arms * Math.atan2(dz, dx) + - winding * radius + phase + y * TAU * 0.5D); + sum += weight * radius / (radius + 0.06D) * wave; + weightSum += weight; + } + } + return 0.5D + 0.5D * sum / weightSum; + } + + private long hash(long x, long z) { + return mix(seed ^ x * 0x9E3779B97F4A7C15L ^ z * 0xD1B54A32D192ED03L); + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } + + private static double unit(long value) { + return (value >>> 11) * 0x1.0p-53; + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CraterNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CraterNoiseTest.java new file mode 100644 index 000000000..80c452e88 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CraterNoiseTest.java @@ -0,0 +1,82 @@ +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 CraterNoiseTest { + @Test + public void craterCentersSitInsideRaisedSphericalRims() { + CraterNoise noise = new CraterNoise(0L); + double center = noise.noise(0.2D, 0.2D, 0.2D); + + assertTrue(center < 0.4D); + assertTrue(noise.noise(0.56D, 0.2D, 0.2D) > center + 0.2D); + assertTrue(noise.noise(0.2D, 0.56D, 0.2D) > center + 0.2D); + assertTrue(noise.noise(0.2D, 0.2D, 0.56D) > center + 0.2D); + } + + @Test + public void neighboringCellSearchDoesNotLeaveTileSeams() { + double epsilon = 1.0E-8D; + for (long seed : new long[]{0L, 713L, Long.MIN_VALUE}) { + CraterNoise noise = new CraterNoise(seed); + for (int boundary = -3; boundary <= 3; boundary++) { + for (int index = 0; index < 64; index++) { + double first = index * 0.071D - 2D; + double second = index * -0.093D + 3D; + assertEquals(noise.noise(boundary - epsilon, first, second), + noise.noise(boundary + epsilon, first, second), 1.0E-5D); + assertEquals(noise.noise(first, boundary - epsilon, second), + noise.noise(first, boundary + epsilon, second), 1.0E-5D); + assertEquals(noise.noise(first, second, boundary - epsilon), + noise.noise(first, second, boundary + epsilon), 1.0E-5D); + } + } + } + } + + @Test + public void horizontalSliceHasDistinctBowlsAndAnnularRims() { + CraterNoise noise = new CraterNoise(713L); + CraterNoise otherSeed = new CraterNoise(714L); + double minimum = 1D; + double maximum = 0D; + double heightVariation = 0D; + double seedVariation = 0D; + + for (int index = 0; index < 1024; index++) { + double x = index * 0.037D - 4D; + double z = index * -0.051D + 7D; + double value = noise.noise(x, z); + assertEquals(value, noise.noise(x, 0D, z), 0D); + minimum = Math.min(minimum, value); + maximum = Math.max(maximum, value); + heightVariation += Math.abs(value - noise.noise(x, 1.25D, z)); + seedVariation += Math.abs(noise.noise(x) - otherSeed.noise(x)); + } + + assertTrue(minimum < 0.3D); + assertTrue(maximum > 0.7D); + assertTrue(heightVariation > 10D); + assertTrue(seedVariation > 10D); + } + + @Test + public void octaveSamplesStayBoundedAndRepeatableAtWorldBorders() { + CraterNoise first = new CraterNoise(Long.MAX_VALUE); + CraterNoise second = new CraterNoise(Long.MAX_VALUE); + + for (int octaves : new int[]{1, 4, 16}) { + first.setOctaves(octaves); + second.setOctaves(octaves); + for (double coordinate : new double[]{-468750D, -1D, -0D, 0.125D, 1D, 468750D}) { + double value = first.noise(coordinate, coordinate * 0.37D, coordinate * -0.71D); + assertTrue(Double.isFinite(value)); + assertTrue(value >= 0D && value <= 1D); + assertEquals(value, second.noise(coordinate, coordinate * 0.37D, coordinate * -0.71D), 0D); + } + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java new file mode 100644 index 000000000..e2e88dfbb --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java @@ -0,0 +1,110 @@ +package art.arcane.iris.util.project.noise; + +import art.arcane.iris.engine.object.NoiseStyle; +import art.arcane.volmlib.util.math.RNG; +import org.junit.Test; + +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PatternNoiseTest { + private static final NoiseStyle[] STYLES = { + NoiseStyle.GYROID, NoiseStyle.QUASICRYSTAL, NoiseStyle.TRUCHET, + NoiseStyle.CRATER, NoiseStyle.VORTEX + }; + + @Test + public void patternsRemainBoundedAtEveryOctaveCountAndWorldEdge() { + Random random = new Random(1337L); + for (long seed : new long[]{0L, 1337L, Long.MIN_VALUE, Long.MAX_VALUE}) { + for (PatternNoise noise : patterns(seed)) { + for (int octaves : new int[]{1, 2, 5, 16}) { + noise.setOctaves(octaves); + for (int sample = 0; sample < 128; sample++) { + double x = (random.nextDouble() * 2D - 1D) * 30_000_000D / 64D; + double y = (random.nextDouble() * 2D - 1D) * 2048D / 64D; + double z = (random.nextDouble() * 2D - 1D) * 30_000_000D / 64D; + assertUnit(noise, noise.noise(x)); + assertUnit(noise, noise.noise(x, z)); + assertUnit(noise, noise.noise(x, y, z)); + assertEquals(noise.noise(x, z), noise.noise(x, 0D, z), 0D); + assertEquals(noise.noise(x), noise.noise(x, 0D), 0D); + } + } + } + } + } + + @Test + public void allPatternsUseTheFullSeedAndVaryThroughHeight() { + PatternNoise[] lowSeed = patterns(1337L); + PatternNoise[] sameSeed = patterns(1337L); + PatternNoise[] highSeed = patterns(1337L + (1L << 32)); + for (int index = 0; index < lowSeed.length; index++) { + double seedDifference = 0D; + double heightDifference = 0D; + for (int sample = 0; sample < 128; sample++) { + double x = sample * 0.173D - 8D; + double z = sample * -0.193D + 7D; + double value = lowSeed[index].noise(x, 0.371D, z); + assertEquals(value, sameSeed[index].noise(x, 0.371D, z), 0D); + seedDifference += Math.abs(value - highSeed[index].noise(x, 0.371D, z)); + heightDifference += Math.abs(value - lowSeed[index].noise(x, 0.891D, z)); + } + assertTrue(lowSeed[index].getClass().getSimpleName(), seedDifference > 1D); + assertTrue(lowSeed[index].getClass().getSimpleName(), heightDifference > 1D); + } + } + + @Test + public void octavesAddDetailWithoutChangingTheBaseSeed() { + for (PatternNoise noise : patterns(42L)) { + double[] original = new double[128]; + for (int index = 0; index < original.length; index++) { + original[index] = noise.noise(index * 0.073D, 0.419D, index * -0.113D); + } + noise.setOctaves(4); + double difference = 0D; + for (int index = 0; index < original.length; index++) { + difference += Math.abs(original[index] - noise.noise(index * 0.073D, 0.419D, index * -0.113D)); + } + assertTrue(noise.getClass().getSimpleName(), difference > 1D); + noise.setOctaves(0); + for (int index = 0; index < original.length; index++) { + assertEquals(original[index], noise.noise(index * 0.073D, 0.419D, index * -0.113D), 0D); + } + noise.setOctaves(16); + double maximum = noise.noise(0.713D, 0.931D, -0.719D); + noise.setOctaves(Integer.MAX_VALUE); + assertEquals(maximum, noise.noise(0.713D, 0.931D, -0.719D), 0D); + } + } + + @Test + public void styleIntegrationUsesTheSameScaleInEveryDimension() { + for (NoiseStyle style : STYLES) { + CNG direct = style.create(new RNG(7331L)); + CNG zoomed = style.style().zoomed(2D).createNoCache(new RNG(7331L), null); + for (int index = 0; index < 128; index++) { + double x = index * 13.713D - 400D; + double y = index * 1.119D - 70D; + double z = index * -17.179D + 300D; + assertEquals(style.name(), direct.noise(x, z), zoomed.noise(x * 2D, z * 2D), 1E-10D); + assertEquals(style.name(), direct.noise(x, y, z), zoomed.noise(x * 2D, y * 2D, z * 2D), 1E-10D); + assertEquals(style.name(), direct.noise(x, z), direct.noise(x, 0D, z), 0D); + } + } + } + + private static PatternNoise[] patterns(long seed) { + return new PatternNoise[]{new GyroidNoise(seed), new QuasicrystalNoise(seed), + new TruchetNoise(seed), new CraterNoise(seed), new VortexNoise(seed)}; + } + + private static void assertUnit(PatternNoise noise, double value) { + assertTrue(noise.getClass().getSimpleName() + ": " + value, + Double.isFinite(value) && value >= 0D && value <= 1D); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/TruchetNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/TruchetNoiseTest.java new file mode 100644 index 000000000..9925a17ff --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/TruchetNoiseTest.java @@ -0,0 +1,70 @@ +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 TruchetNoiseTest { + @Test + public void quarterCircleRibbonsMeetAcrossTileBoundaries() { + TruchetNoise xBoundaryNoise = new TruchetNoise(0L); + TruchetNoise zBoundaryNoise = new TruchetNoise(-0x9E3779B97F4A7C15L); + double epsilon = 1.0E-8D; + double maximum = 0D; + + for (int cell = -3; cell <= 3; cell++) { + for (int index = 0; index <= 2048; index++) { + double position = index / 512D - 2D; + double x = cell - 0.17D; + double z = cell - 0.13D; + assertEquals(xBoundaryNoise.noise(x - epsilon, position), + xBoundaryNoise.noise(x + epsilon, position), 1.0E-5D); + assertEquals(zBoundaryNoise.noise(position, z - epsilon), + zBoundaryNoise.noise(position, z + epsilon), 1.0E-5D); + maximum = Math.max(maximum, xBoundaryNoise.noise(x, position)); + } + } + + assertTrue(maximum > 0.999D); + } + + @Test + public void helicalContinuationIsContinuousThroughTheHorizontalSlice() { + TruchetNoise noise = new TruchetNoise(713L); + double heightVariation = 0D; + double seedVariation = 0D; + TruchetNoise otherSeed = new TruchetNoise(714L); + + for (int index = 0; index < 256; index++) { + double x = index * 0.037D - 4D; + double z = index * -0.051D + 7D; + double horizontal = noise.noise(x, z); + assertEquals(horizontal, noise.noise(x, 0D, z), 0D); + assertEquals(horizontal, noise.noise(x, -1.0E-8D, z), 1.0E-5D); + assertEquals(horizontal, noise.noise(x, 1.0E-8D, z), 1.0E-5D); + heightVariation += Math.abs(horizontal - noise.noise(x, 1.25D, z)); + seedVariation += Math.abs(noise.noise(x) - otherSeed.noise(x)); + } + + assertTrue(heightVariation > 10D); + assertTrue(seedVariation > 10D); + } + + @Test + public void octavesStayBoundedAndRepeatableAtWorldBorders() { + TruchetNoise first = new TruchetNoise(Long.MIN_VALUE); + TruchetNoise second = new TruchetNoise(Long.MIN_VALUE); + + for (int octaves : new int[]{1, 4, 16}) { + first.setOctaves(octaves); + second.setOctaves(octaves); + for (double coordinate : new double[]{-468750D, -1D, -0D, 0.125D, 1D, 468750D}) { + double value = first.noise(coordinate, coordinate * 0.37D, coordinate * -0.71D); + assertTrue(Double.isFinite(value)); + assertTrue(value >= 0D && value <= 1D); + assertEquals(value, second.noise(coordinate, coordinate * 0.37D, coordinate * -0.71D), 0D); + } + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/VortexNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/VortexNoiseTest.java new file mode 100644 index 000000000..a5bf94ed8 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/VortexNoiseTest.java @@ -0,0 +1,37 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class VortexNoiseTest { + @Test + public void spiralsStayContinuousAcrossCellBoundaries() { + for (long seed : new long[]{0L, 42L, Long.MAX_VALUE}) { + VortexNoise noise = new VortexNoise(seed); + for (int cell = -8; cell <= 8; cell++) { + for (int index = 0; index < 64; index++) { + double coordinate = index * 0.0713D - 2D; + assertEquals(noise.noise(cell - 1E-7D, 0.317D, coordinate), + noise.noise(cell + 1E-7D, 0.317D, coordinate), 1E-5D); + assertEquals(noise.noise(coordinate, 0.317D, cell - 1E-7D), + noise.noise(coordinate, 0.317D, cell + 1E-7D), 1E-5D); + } + } + } + } + + @Test + public void spiralCentersAndAngleWrapHaveNoJump() { + VortexNoise noise = new VortexNoise(0L); + double center = noise.noise(0.2D, 0.419D, 0.2D); + for (int index = 0; index < 128; index++) { + double angle = index * Math.PI / 64D; + assertEquals(center, noise.noise(0.2D + Math.cos(angle) * 1E-8D, 0.419D, + 0.2D + Math.sin(angle) * 1E-8D), 1E-6D); + double x = 0.2D - index * 0.007D; + assertEquals(noise.noise(x, 0.419D, 0.2D - 1E-8D), + noise.noise(x, 0.419D, 0.2D + 1E-8D), 1E-6D); + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/WavePatternNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/WavePatternNoiseTest.java new file mode 100644 index 000000000..35011d12d --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/WavePatternNoiseTest.java @@ -0,0 +1,85 @@ +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 WavePatternNoiseTest { + private static final long SEED = 813753L; + + @Test + public void wavePatternsAreSeededBoundedAndContinuousInEveryAxis() { + for (boolean gyroid : new boolean[]{true, false}) { + NoiseGenerator first = gyroid ? new GyroidNoise(SEED) : new QuasicrystalNoise(SEED); + NoiseGenerator repeated = gyroid ? new GyroidNoise(SEED) : new QuasicrystalNoise(SEED); + NoiseGenerator otherSeed = gyroid ? new GyroidNoise(SEED + 1) : new QuasicrystalNoise(SEED + 1); + double seedDifference = 0D; + double horizontalDifference = 0D; + double verticalDifference = 0D; + double depthDifference = 0D; + for (double origin : new double[]{-30_000_000D / 64D, 0D, 30_000_000D / 64D}) { + for (int i = 0; i < 128; i++) { + double x = origin + i * 0.137D; + double y = i * 0.193D - 7D; + double z = -origin + i * 0.317D; + double value = first.noise(x, y, z); + assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D); + assertEquals(value, repeated.noise(x, y, z), 0D); + assertEquals(value, first.noise(x + 1E-6D, y, z), 1E-3D); + assertEquals(value, first.noise(x, y + 1E-6D, z), 1E-3D); + assertEquals(value, first.noise(x, y, z + 1E-6D), 1E-3D); + seedDifference += Math.abs(value - otherSeed.noise(x, y, z)); + horizontalDifference += Math.abs(value - first.noise(x + 0.137D, y, z)); + verticalDifference += Math.abs(value - first.noise(x, y + 0.137D, z)); + depthDifference += Math.abs(value - first.noise(x, y, z + 0.137D)); + } + } + assertTrue(seedDifference > 10D); + assertTrue(horizontalDifference > 10D); + assertTrue(verticalDifference > 10D); + assertTrue(depthDifference > 10D); + } + } + + @Test + public void quasicrystalPreservesFivefoldRotationalSymmetryAcrossLayers() { + QuasicrystalNoise noise = new QuasicrystalNoise(SEED); + double angle = Math.PI * 2D / 5D; + double cosine = Math.cos(angle); + double sine = Math.sin(angle); + for (int i = 0; i < 128; i++) { + double x = i * 0.137D - 8D; + double y = i * 0.031D; + double z = i * 0.219D - 13D; + double rotatedX = x * cosine - z * sine; + double rotatedZ = x * sine + z * cosine; + assertEquals(noise.noise(x, y, z), noise.noise(rotatedX, y, rotatedZ), 1E-12D); + } + assertNotEquals(noise.noise(0.19D, -0.37D), noise.noise(1.19D, -0.37D), 1E-4D); + } + + @Test + public void gyroidContainsBothOpenSpacesAndBrightSheets() { + GyroidNoise noise = new GyroidNoise(SEED); + int open = 0; + int sheet = 0; + int gradient = 0; + for (int x = -64; x < 64; x++) { + for (int z = -64; z < 64; z++) { + double value = noise.noise(x * 0.127D, z * 0.127D); + if (value < 0.05D) { + open++; + } else if (value > 0.8D) { + sheet++; + } else { + gradient++; + } + } + } + assertTrue(open > 1638 && open < 13_107); + assertTrue(sheet > 819); + assertTrue(gradient > 1638); + } +} From 14cfe3ed68b101fb497e201cfc915cb47ff8ed08 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sat, 5 Sep 2026 19:33:53 -0400 Subject: [PATCH 3/3] Added diverse noise styles - Dune and Strata add dunes and layered bands - Wood and Marble add warped grain and mineral veins - Gabor and Scales add oriented waves and overlapping scallops - Chladni and Kaleidoscope add nodal lines and mirrored motifs - Menger Sponge and Circuit add recursive voids and connected traces - New styles use the shared scale and full seeds - Tests cover tile seams, volume variation, bounds, and octaves --- .../arcane/iris/engine/object/NoiseStyle.java | 30 +++++ .../iris/util/project/noise/ChladniNoise.java | 34 ++++++ .../iris/util/project/noise/CircuitNoise.java | 73 +++++++++++ .../iris/util/project/noise/DuneNoise.java | 61 ++++++++++ .../iris/util/project/noise/GaborNoise.java | 72 +++++++++++ .../util/project/noise/KaleidoscopeNoise.java | 43 +++++++ .../iris/util/project/noise/MarbleNoise.java | 36 ++++++ .../util/project/noise/MengerSpongeNoise.java | 48 ++++++++ .../iris/util/project/noise/NoiseType.java | 12 +- .../iris/util/project/noise/ScalesNoise.java | 61 ++++++++++ .../iris/util/project/noise/StrataNoise.java | 37 ++++++ .../iris/util/project/noise/WoodNoise.java | 56 +++++++++ .../util/project/noise/ChladniNoiseTest.java | 69 +++++++++++ .../util/project/noise/CircuitNoiseTest.java | 45 +++++++ .../util/project/noise/GaborNoiseTest.java | 40 ++++++ .../project/noise/KaleidoscopeNoiseTest.java | 92 ++++++++++++++ .../util/project/noise/MarbleNoiseTest.java | 29 +++++ .../noise/MaterialPatternNoiseTest.java | 114 ++++++++++++++++++ .../project/noise/MengerSpongeNoiseTest.java | 83 +++++++++++++ .../util/project/noise/PatternNoiseTest.java | 8 +- .../util/project/noise/ScalesNoiseTest.java | 38 ++++++ .../util/project/noise/TextureNoiseTest.java | 105 ++++++++++++++++ 22 files changed, 1183 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/ChladniNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/CircuitNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/DuneNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/GaborNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/KaleidoscopeNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/MarbleNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/MengerSpongeNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/ScalesNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/StrataNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/WoodNoise.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/ChladniNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/CircuitNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/GaborNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/KaleidoscopeNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/MarbleNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/MaterialPatternNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/MengerSpongeNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/ScalesNoiseTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/noise/TextureNoiseTest.java 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 3febe384a..ec249d7d4 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 @@ -553,6 +553,36 @@ public enum NoiseStyle { @Desc("Seeded spiral arms form swirling eddies in 2D and winding funnels in 3D.") VORTEX(rng -> new CNG(rng, NoiseType.VORTEX, 1D, 1), NoiseType.VORTEX), + + @Desc("Seeded crescent dunes with asymmetric slopes and smooth height-dependent drift.") + DUNE(rng -> new CNG(rng, NoiseType.DUNE, 1D, 1), NoiseType.DUNE), + + @Desc("Folded sedimentary bands with varying thickness through the volume.") + STRATA(rng -> new CNG(rng, NoiseType.STRATA, 1D, 1), NoiseType.STRATA), + + @Desc("Distorted growth rings and knot-like forms with variation through height.") + WOOD(rng -> new CNG(rng, NoiseType.WOOD, 1D, 1), NoiseType.WOOD), + + @Desc("Seeded oriented wave packets form a sparse directional ripple texture.") + GABOR(rng -> new CNG(rng, NoiseType.GABOR, 1D, 1), NoiseType.GABOR), + + @Desc("Turbulent stone veins separate broad smooth regions.") + MARBLE(rng -> new CNG(rng, NoiseType.MARBLE, 1D, 1), NoiseType.MARBLE), + + @Desc("Overlapping scalloped scales form a seeded tiled surface.") + SCALES(rng -> new CNG(rng, NoiseType.SCALES, 1D, 1), NoiseType.SCALES), + + @Desc("Standing-wave nodes form plate-like figures that change through height.") + CHLADNI(rng -> new CNG(rng, NoiseType.CHLADNI, 1D, 1), NoiseType.CHLADNI), + + @Desc("Mirrored wedge motifs repeat at a local scale with seeded variation.") + KALEIDOSCOPE(rng -> new CNG(rng, NoiseType.KALEIDOSCOPE, 1D, 1), NoiseType.KALEIDOSCOPE), + + @Desc("Recursive cubic voids form a three-dimensional sponge with square-hole slices.") + MENGER_SPONGE(rng -> new CNG(rng, NoiseType.MENGER_SPONGE, 1D, 1), NoiseType.MENGER_SPONGE), + + @Desc("Orthogonal tracks and ring pads form connected circuits that shift through height.") + CIRCUIT(rng -> new CNG(rng, NoiseType.CIRCUIT, 1D, 1), NoiseType.CIRCUIT), ; private final CNGFactory f; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/ChladniNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/ChladniNoise.java new file mode 100644 index 000000000..5a6cc6e01 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/ChladniNoise.java @@ -0,0 +1,34 @@ +package art.arcane.iris.util.project.noise; + +public final class ChladniNoise extends PatternNoise { + private final int firstMode; + private final int secondMode; + private final double phase; + private final double width; + + public ChladniNoise(long seed) { + long hash = mix(seed); + firstMode = 2 + (int) (hash & 1L); + secondMode = firstMode + 1 + (int) ((hash >>> 1) & 1L); + phase = (hash >>> 11) * 0x1.0p-52D; + width = 0.22D + ((hash >>> 3) & 255L) * (0.12D / 255D); + } + + @Override + protected double sample(double x, double y, double z) { + double u = (x + phase) * Math.PI; + double v = (z + phase) * Math.PI; + double first = Math.cos(firstMode * u) * Math.cos(secondMode * v); + double second = Math.cos(secondMode * u) * Math.cos(firstMode * v); + double field = first * Math.cos(y * Math.PI * 0.5D) + - second * Math.cos(y * Math.PI * 0.75D); + double node = Math.max(0D, 1D - Math.abs(field) / width); + return node * node * (3D - 2D * node); + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CircuitNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/CircuitNoise.java new file mode 100644 index 000000000..978ef670a --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CircuitNoise.java @@ -0,0 +1,73 @@ +package art.arcane.iris.util.project.noise; + +public final class CircuitNoise extends PatternNoise { + private final long seed; + private final double phaseX; + private final double phaseZ; + + public CircuitNoise(long seed) { + this.seed = seed; + phaseX = (mix(seed) >>> 11) * 0x1.0p-53; + phaseZ = (mix(seed + 0x9E3779B97F4A7C15L) >>> 11) * 0x1.0p-53; + } + + @Override + protected double sample(double x, double y, double z) { + x += phaseX; + z += phaseZ; + if (y != 0D) { + x += y * 0.13D + 0.17D * Math.sin(y * Math.PI * 0.5D); + z += 0.11D * Math.sin(y * Math.PI * 0.75D); + } + long cellX = (long) Math.floor(x); + long cellZ = (long) Math.floor(z); + x -= cellX; + z -= cellZ; + long hash = mix(seed ^ cellX * 0x9E3779B97F4A7C15L ^ cellZ * 0xD1B54A32D192ED03L); + double distance; + double padDistance; + if ((hash & 3L) == 0L) { + distance = Math.min(Math.abs(x - 0.5D), Math.abs(z - 0.5D)); + double dx = x - 0.5D; + double dz = z - 0.5D; + padDistance = Math.sqrt(dx * dx + dz * dz); + } else { + if ((hash & 1L) != 0L) { + x = 1D - x; + } + distance = Math.sqrt(Math.min(routeDistanceSquared(x, z), routeDistanceSquared(1D - x, 1D - z))); + double dx = x - 0.75D; + double dz = z - 0.25D; + double oppositeX = x - 0.25D; + double oppositeZ = z - 0.75D; + padDistance = Math.sqrt(Math.min(dx * dx + dz * dz, oppositeX * oppositeX + oppositeZ * oppositeZ)); + } + double trace = smooth(1D - distance / 0.045D); + double pad = smooth(1D - Math.abs(padDistance - 0.085D) / 0.03D); + return Math.max(trace, pad) * smooth(padDistance / 0.045D); + } + + private static double routeDistanceSquared(double x, double z) { + double top = segmentDistanceSquared(x - 0.5D, z, 0D, 0.25D); + double bend = segmentDistanceSquared(z - 0.25D, x, 0.5D, 0.75D); + double middle = segmentDistanceSquared(x - 0.75D, z, 0.25D, 0.5D); + double right = segmentDistanceSquared(z - 0.5D, x, 0.75D, 1D); + return Math.min(Math.min(top, bend), Math.min(middle, right)); + } + + private static double segmentDistanceSquared(double perpendicular, double along, double start, double end) { + double excess = Math.max(start - along, Math.max(along - end, 0D)); + return perpendicular * perpendicular + excess * excess; + } + + private static double smooth(double value) { + double bounded = Math.clamp(value, 0D, 1D); + return bounded * bounded * (3D - 2D * bounded); + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/DuneNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/DuneNoise.java new file mode 100644 index 000000000..2472da3c4 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/DuneNoise.java @@ -0,0 +1,61 @@ +package art.arcane.iris.util.project.noise; + +public final class DuneNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + + private final long seed; + private final double cosine; + private final double sine; + private final double phase; + + public DuneNoise(long seed) { + this.seed = seed; + phase = unit(mix(seed)) * TAU; + cosine = Math.cos(phase); + sine = Math.sin(phase); + } + + @Override + protected double sample(double x, double y, double z) { + double wind = x * cosine + z * sine + y * 0.17D; + double across = z * cosine - x * sine + 0.12D * Math.sin(y * 0.7D + phase); + long cellWind = (long) Math.floor(wind); + long cellAcross = (long) Math.floor(across); + double dune = 0D; + for (int offsetWind = -1; offsetWind <= 1; offsetWind++) { + long gridWind = cellWind + offsetWind; + for (int offsetAcross = -1; offsetAcross <= 1; offsetAcross++) { + long gridAcross = cellAcross + offsetAcross; + long hash = mix(seed ^ gridWind * 0x9E3779B97F4A7C15L ^ gridAcross * 0xD1B54A32D192ED03L); + double centerWind = gridWind + 0.2D + (hash & 65535L) * (0.6D / 65535D); + double centerAcross = gridAcross + 0.2D + ((hash >>> 16) & 65535L) * (0.6D / 65535D); + double width = 0.32D + ((hash >>> 32) & 255L) * (0.16D / 255D); + double transverse = (across - centerAcross) / width; + double transverseSquared = transverse * transverse; + if (transverseSquared >= 1D) { + continue; + } + double crest = centerWind + 0.43D * transverseSquared; + double offset = wind - crest; + double windward = 0.52D + ((hash >>> 40) & 255L) * (0.14D / 255D); + double leeward = 0.1D + ((hash >>> 48) & 255L) * (0.035D / 255D); + double slope = Math.max(0D, 1D - Math.abs(offset) / (offset < 0D ? windward : leeward)); + double horns = 1D - transverseSquared * transverseSquared; + double height = 0.62D + (hash >>> 56) * (0.38D / 255D); + dune = Math.max(dune, height * horns * horns * slope * slope * (3D - 2D * slope)); + } + } + double ripples = 0.04D + 0.035D * Math.cos(wind * TAU * 2D + 0.5D * Math.sin(across * 1.7D + phase)); + return ripples + (1D - ripples) * dune; + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } + + private static double unit(long value) { + return (value >>> 11) * 0x1.0p-53D; + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/GaborNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/GaborNoise.java new file mode 100644 index 000000000..2fd5e7bc9 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/GaborNoise.java @@ -0,0 +1,72 @@ +package art.arcane.iris.util.project.noise; + +import java.util.SplittableRandom; + +public final class GaborNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + private static final double RADIUS_SQUARED = 0.95D * 0.95D; + private static final double SPREAD = 0.6D / 65535D; + + private final long seed; + private final double directionX; + private final double directionY; + private final double directionZ; + + public GaborNoise(long seed) { + this.seed = seed; + SplittableRandom random = new SplittableRandom(seed); + double angle = random.nextDouble(TAU); + double elevation = random.nextDouble(-0.55D, 0.55D); + double horizontal = Math.sqrt(1D - elevation * elevation); + directionX = Math.cos(angle) * horizontal; + directionY = elevation; + directionZ = Math.sin(angle) * horizontal; + } + + @Override + protected double sample(double x, double y, double z) { + long cellX = (long) Math.floor(x); + long cellY = (long) Math.floor(y); + long cellZ = (long) Math.floor(z); + double sum = 0D; + double weights = 0.12D; + for (int offsetY = -1; offsetY <= 1; offsetY++) { + long pointY = cellY + offsetY; + long hashY = pointY * 0xC2B2AE3D27D4EB4FL; + for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { + long pointZ = cellZ + offsetZ; + long hashYZ = hashY ^ pointZ * 0x165667B19E3779F9L; + for (int offsetX = -1; offsetX <= 1; offsetX++) { + long pointX = cellX + offsetX; + long hash = mix(seed ^ pointX * 0x9E3779B97F4A7C15L ^ hashYZ); + if ((hash & 3L) == 0L) { + continue; + } + double dx = x - (pointX + 0.2D + (hash & 65535L) * SPREAD); + double dy = y - (pointY + 0.2D + ((hash >>> 16) & 65535L) * SPREAD); + double dz = z - (pointZ + 0.2D + ((hash >>> 32) & 65535L) * SPREAD); + double distanceSquared = dx * dx + dy * dy + dz * dz; + if (distanceSquared >= RADIUS_SQUARED) { + continue; + } + double envelope = 1D - distanceSquared / RADIUS_SQUARED; + double weight = envelope * envelope * envelope; + long wave = mix(hash); + double waveX = directionX + ((wave & 1023L) / 1023D - 0.5D) * 0.35D; + double waveY = directionY + (((wave >>> 10) & 1023L) / 1023D - 0.5D) * 0.35D; + double waveZ = directionZ + (((wave >>> 20) & 1023L) / 1023D - 0.5D) * 0.35D; + double phase = (wave >>> 32) * (TAU / 4294967296D); + sum += weight * Math.cos(TAU * 3D * (waveX * dx + waveY * dy + waveZ * dz) + phase); + weights += weight; + } + } + } + return 0.5D + 0.5D * sum / weights; + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/KaleidoscopeNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/KaleidoscopeNoise.java new file mode 100644 index 000000000..3bde2b2ad --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/KaleidoscopeNoise.java @@ -0,0 +1,43 @@ +package art.arcane.iris.util.project.noise; + +public final class KaleidoscopeNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + + private final long seed; + + public KaleidoscopeNoise(long seed) { + this.seed = seed; + } + + @Override + protected double sample(double x, double y, double z) { + z += 0.5D; + long cellX = (long) Math.floor(x); + long cellZ = (long) Math.floor(z); + double dx = x - cellX - 0.5D; + double dz = z - cellZ - 0.5D; + double radiusSquared = dx * dx + dz * dz; + if (radiusSquared >= 0.25D) { + return 0D; + } + + long hash = mix(seed ^ cellX * 0x9E3779B97F4A7C15L ^ cellZ * 0xD1B54A32D192ED03L); + int wedges = 4 + (int) (hash & 3L); + double phase = (hash >>> 11) * 0x1.0p-53D * TAU; + double sector = TAU / wedges; + double angle = Math.atan2(dz, dx) + phase + y * 0.35D; + angle = Math.abs(angle - Math.floor(angle / sector + 0.5D) * sector); + double radius = Math.sqrt(radiusSquared); + double u = radius * Math.cos(angle); + double v = radius * Math.sin(angle); + double motif = 0.5D + 0.5D * Math.cos(16D * u + 2.5D * Math.cos(18D * v + phase + y)); + double envelope = 1D - radiusSquared * 4D; + return motif * envelope * envelope; + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/MarbleNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/MarbleNoise.java new file mode 100644 index 000000000..7d58edd2c --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/MarbleNoise.java @@ -0,0 +1,36 @@ +package art.arcane.iris.util.project.noise; + +import java.util.SplittableRandom; + +public final class MarbleNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + + private final FastNoiseDouble distortion; + private final double directionX; + private final double directionY; + private final double directionZ; + private final double phase; + + public MarbleNoise(long seed) { + SplittableRandom random = new SplittableRandom(seed); + distortion = new FastNoiseDouble(random.nextLong()); + distortion.setFrequency(1D); + double angle = random.nextDouble(TAU); + directionX = Math.cos(angle); + directionY = random.nextDouble(0.25D, 0.65D); + directionZ = Math.sin(angle); + phase = random.nextDouble(TAU); + } + + @Override + protected double sample(double x, double y, double z) { + double broad = distortion.GetSimplex(x * 0.45D, y * 0.45D, z * 0.45D); + double detail = distortion.GetSimplex(x * 1.3D + 17.3D, y * 1.3D - 11.7D, z * 1.3D + 5.9D); + double warp = broad * 1.1D + detail * 0.2D; + double position = x * directionX + y * directionY + z * directionZ; + double ridge = Math.sin(TAU * (position + warp) + phase); + double vein = 1D / (1D + 90D * ridge * ridge); + double stone = 0.85D + 0.15D * broad; + return Math.clamp(0.1D + 0.84D * stone * (1D - vein), 0D, 1D); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/MengerSpongeNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/MengerSpongeNoise.java new file mode 100644 index 000000000..4505dc21a --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/MengerSpongeNoise.java @@ -0,0 +1,48 @@ +package art.arcane.iris.util.project.noise; + +public final class MengerSpongeNoise extends PatternNoise { + private static final int DEPTH = 3; + + private final double phaseX; + private final double phaseY; + private final double phaseZ; + + public MengerSpongeNoise(long seed) { + long hash = mix(seed); + phaseX = (hash & 0x1FFFFFL) * 0x1.0p-21D; + phaseY = ((hash >>> 21) & 0x1FFFFFL) * (0x1.0p-21D / 3D); + phaseZ = (hash >>> 42) * 0x1.0p-22D; + } + + @Override + protected double sample(double x, double y, double z) { + x += phaseX; + y += phaseY; + z += phaseZ; + x -= Math.floor(x); + y -= Math.floor(y); + z -= Math.floor(z); + for (int level = 0; level < DEPTH; level++) { + x *= 3D; + y *= 3D; + z *= 3D; + int cellX = (int) x; + int cellY = (int) y; + int cellZ = (int) z; + int middleAxes = (cellX == 1 ? 1 : 0) + (cellY == 1 ? 1 : 0) + (cellZ == 1 ? 1 : 0); + if (middleAxes >= 2) { + return 0D; + } + x -= cellX; + y -= cellY; + z -= cellZ; + } + return 1D; + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} 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 78c6e0079..7c3867bdd 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 @@ -92,7 +92,17 @@ public enum NoiseType { QUASICRYSTAL(QuasicrystalNoise::new, 1D / 64D), TRUCHET(TruchetNoise::new, 1D / 64D), CRATER(CraterNoise::new, 1D / 64D), - VORTEX(VortexNoise::new, 1D / 64D); + VORTEX(VortexNoise::new, 1D / 64D), + DUNE(DuneNoise::new, 1D / 64D), + STRATA(StrataNoise::new, 1D / 64D), + WOOD(WoodNoise::new, 1D / 64D), + GABOR(GaborNoise::new, 1D / 64D), + MARBLE(MarbleNoise::new, 1D / 64D), + SCALES(ScalesNoise::new, 1D / 64D), + CHLADNI(ChladniNoise::new, 1D / 64D), + KALEIDOSCOPE(KaleidoscopeNoise::new, 1D / 64D), + MENGER_SPONGE(MengerSpongeNoise::new, 1D / 64D), + CIRCUIT(CircuitNoise::new, 1D / 64D); private final NoiseFactory f; private final double coordinateScale; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/ScalesNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/ScalesNoise.java new file mode 100644 index 000000000..053a6cfc2 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/ScalesNoise.java @@ -0,0 +1,61 @@ +package art.arcane.iris.util.project.noise; + +public final class ScalesNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + private static final double RADIUS = 0.5D; + private static final double ROW_HEIGHT = 0.5D; + private static final double GROOVE_WIDTH = 0.055D; + + private final long seed; + private final double phaseX; + private final double phaseZ; + + public ScalesNoise(long seed) { + this.seed = seed; + phaseX = (mix(seed) >>> 11) * 0x1.0p-53D; + phaseZ = (mix(seed + 0x9E3779B97F4A7C15L) >>> 11) * 0x1.0p-53D; + } + + @Override + protected double sample(double x, double y, double z) { + x += phaseX + Math.sin(y * TAU * 0.5D) * 0.22D + y * 0.15D; + z += phaseZ + Math.sin(y * TAU * 0.25D) * 0.12D + y * 0.2D; + long row = (long) Math.floor(z / ROW_HEIGHT); + double groove = 0D; + double shade = 0D; + for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { + long pointZ = row + offsetZ; + double shift = (pointZ & 1L) * 0.5D; + long column = (long) Math.floor(x - shift); + double dz = z - pointZ * ROW_HEIGHT; + for (int offsetX = -1; offsetX <= 1; offsetX++) { + long pointX = column + offsetX; + double dx = x - (pointX + shift); + double radiusSquared = dx * dx + dz * dz; + double distance; + if (dz >= 0D) { + distance = Math.abs(Math.sqrt(radiusSquared) - RADIUS); + } else { + double endpoint = Math.abs(dx) - RADIUS; + distance = Math.sqrt(endpoint * endpoint + dz * dz); + } + double rim = Math.max(0D, 1D - distance / GROOVE_WIDTH); + if (rim > 0D || radiusSquared < RADIUS * RADIUS && dz > 0D) { + long hash = mix(seed ^ pointX * 0x9E3779B97F4A7C15L ^ pointZ * 0xC2B2AE3D27D4EB4FL); + double variation = 0.75D + (hash >>> 11) * 0x1.0p-53D * 0.25D; + groove = Math.max(groove, rim * rim * (3D - 2D * rim) * variation); + double inside = Math.max(0D, 1D - radiusSquared / (RADIUS * RADIUS)); + double rise = Math.clamp(dz / 0.12D, 0D, 1D); + shade = Math.max(shade, inside * rise * rise * (3D - 2D * rise) * variation); + } + } + } + return 0.72D + 0.22D * shade - 0.65D * groove; + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/StrataNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/StrataNoise.java new file mode 100644 index 000000000..b5ccec2ae --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/StrataNoise.java @@ -0,0 +1,37 @@ +package art.arcane.iris.util.project.noise; + +import java.util.SplittableRandom; + +public final class StrataNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + + private final double cosine; + private final double sine; + private final double phaseA; + private final double phaseB; + private final double phaseC; + + public StrataNoise(long seed) { + SplittableRandom random = new SplittableRandom(seed); + double angle = random.nextDouble(TAU); + cosine = Math.cos(angle); + sine = Math.sin(angle); + phaseA = random.nextDouble(TAU); + phaseB = random.nextDouble(TAU); + phaseC = random.nextDouble(TAU); + } + + @Override + protected double sample(double x, double y, double z) { + double along = x * cosine + z * sine; + double across = z * cosine - x * sine; + double fold = 1.1D * Math.sin(along * 1.1D + phaseA) + + 0.35D * Math.sin(along * 2.7D + across * 0.8D + phaseB + y * 0.4D) + + 0.2D * Math.sin(across * 1.7D + y * 0.9D + phaseC); + double layer = TAU * (across + y * 0.75D + fold); + double thickness = layer + 0.55D * Math.sin(layer * 0.25D + phaseB); + double broadBand = 0.5D + 0.5D * Math.sin(thickness); + double lamina = 0.5D + 0.5D * Math.sin(thickness * 3D + phaseC); + return 0.82D * broadBand + 0.18D * lamina; + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/WoodNoise.java b/core/src/main/java/art/arcane/iris/util/project/noise/WoodNoise.java new file mode 100644 index 000000000..4791f0822 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/WoodNoise.java @@ -0,0 +1,56 @@ +package art.arcane.iris.util.project.noise; + +public final class WoodNoise extends PatternNoise { + private static final double TAU = Math.PI * 2D; + + private final long seed; + private final double cosine; + private final double sine; + private final double phase; + + public WoodNoise(long seed) { + this.seed = seed; + phase = (mix(seed) >>> 11) * 0x1.0p-53D * TAU; + cosine = Math.cos(phase); + sine = Math.sin(phase); + } + + @Override + protected double sample(double x, double y, double z) { + double along = (z * cosine - x * sine) * 0.45D + y * 0.17D; + double across = x * cosine + z * sine + y * 0.07D + 0.11D * Math.sin(along * 0.85D + phase); + double grainPhase = across * TAU * 3D + 0.18D * Math.sin(along * 2.1D + phase); + double value = 0.12D * (0.5D + 0.5D * Math.sin(grainPhase)); + double weightSum = 0.12D; + long cellAcross = (long) Math.floor(across); + long cellAlong = (long) Math.floor(along); + for (int offsetAcross = -1; offsetAcross <= 1; offsetAcross++) { + long gridAcross = cellAcross + offsetAcross; + for (int offsetAlong = -1; offsetAlong <= 1; offsetAlong++) { + long gridAlong = cellAlong + offsetAlong; + long hash = mix(seed ^ gridAcross * 0x9E3779B97F4A7C15L ^ gridAlong * 0xD1B54A32D192ED03L); + double deltaAcross = across - (gridAcross + 0.3D + (hash & 65535L) * (0.4D / 65535D)); + double deltaAlong = along - (gridAlong + 0.3D + ((hash >>> 16) & 65535L) * (0.4D / 65535D)); + double radius = 0.5D + ((hash >>> 32) & 255L) * (0.12D / 255D); + double distanceSquared = deltaAcross * deltaAcross + deltaAlong * deltaAlong; + double envelope = 1D - distanceSquared / (radius * radius); + if (envelope <= 0D) { + continue; + } + double rings = Math.sqrt(distanceSquared + 0.0004D) * TAU * 5D; + double ringPhase = ((hash >>> 40) & 65535L) * (TAU / 65535D); + double knot = 0.5D + 0.5D * Math.sin(rings + ringPhase); + double weight = 3D * envelope * envelope; + value += knot * weight; + weightSum += weight; + } + } + return value / weightSum; + } + + private static long mix(long value) { + value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L; + value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL; + return value ^ (value >>> 31); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/ChladniNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/ChladniNoiseTest.java new file mode 100644 index 000000000..ac45b977f --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/ChladniNoiseTest.java @@ -0,0 +1,69 @@ +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 ChladniNoiseTest { + @Test + public void horizontalPlateHasMirroredNodalLinesAndDarkAntinodes() { + for (long seed : new long[]{0L, 1337L, Long.MIN_VALUE}) { + ChladniNoise noise = new ChladniNoise(seed); + double minimum = 1D; + for (int index = 0; index < 256; index++) { + double x = index * 0.031D - 4D; + double z = index * -0.047D + 3D; + assertEquals(1D, noise.noise(x, x), 0D); + assertEquals(noise.noise(x, z), noise.noise(z, x), 1.0E-12D); + minimum = Math.min(minimum, noise.noise(x, z)); + } + assertTrue(minimum < 0.01D); + } + } + + @Test + public void standingModesChangeContinuouslyWithHeight() { + ChladniNoise noise = new ChladniNoise(1337L); + double variation = 0D; + for (int index = 0; index < 512; index++) { + double x = index * 0.019D - 5D; + double z = index * -0.023D + 3D; + assertEquals(noise.noise(x, z), noise.noise(x, 0D, z), 0D); + for (double y : new double[]{-2D, 0D, 1D, 468750D}) { + assertEquals(noise.noise(x, y - 1.0E-8D, z), + noise.noise(x, y + 1.0E-8D, z), 1.0E-5D); + } + variation += Math.abs(noise.noise(x, z) - noise.noise(x, 0.7D, z)); + } + assertTrue(variation > 20D); + } + + @Test + public void upperSeedBitsChangeThePlate() { + ChladniNoise first = new ChladniNoise(1337L); + ChladniNoise second = new ChladniNoise(1337L + (1L << 32)); + double variation = 0D; + for (int index = 0; index < 256; index++) { + variation += Math.abs(first.noise(index * 0.019D) - second.noise(index * 0.019D)); + } + assertTrue(variation > 10D); + } + + @Test + public void octaveSamplesStayBoundedAndRepeatableAtWorldBorders() { + ChladniNoise first = new ChladniNoise(Long.MAX_VALUE); + ChladniNoise second = new ChladniNoise(Long.MAX_VALUE); + for (int octaves : new int[]{1, 3, 16}) { + first.setOctaves(octaves); + second.setOctaves(octaves); + for (int index = 0; index < 128; index++) { + double x = -468750D + index * (937500D / 127D); + double value = first.noise(x, x * 0.37D, x * -0.71D); + assertTrue(Double.isFinite(value)); + assertTrue(value >= 0D && value <= 1D); + assertEquals(value, second.noise(x, x * 0.37D, x * -0.71D), 0D); + } + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CircuitNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CircuitNoiseTest.java new file mode 100644 index 000000000..b7ba068dc --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CircuitNoiseTest.java @@ -0,0 +1,45 @@ +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 CircuitNoiseTest { + @Test + public void tracksMeetAtTilePortsWithoutBoundaryJumps() { + CircuitNoise noise = new CircuitNoise(0L); + double maximum = 0D; + for (int boundary = -8; boundary <= 8; boundary++) { + for (int step = 0; step < 256; step++) { + double z = step * 0.019D - 2D; + assertEquals(noise.noise(boundary - 1E-8D, z), noise.noise(boundary + 1E-8D, z), 1E-5D); + maximum = Math.max(maximum, noise.noise(boundary, z)); + } + } + assertTrue(maximum > 0.99D); + } + + @Test + public void circuitHasOpenBoardTracesAndIntermediateEdges() { + CircuitNoise noise = new CircuitNoise(1337L); + int open = 0; + int trace = 0; + int edge = 0; + for (int x = 0; x < 256; x++) { + for (int z = 0; z < 256; z++) { + double value = noise.noise(x / 64D, z / 64D); + if (value == 0D) { + open++; + } else if (value > 0.8D) { + trace++; + } else { + edge++; + } + } + } + assertTrue(open > 32768); + assertTrue(trace > 1500); + assertTrue(edge > 1500); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/GaborNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/GaborNoiseTest.java new file mode 100644 index 000000000..c0d48f2d4 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/GaborNoiseTest.java @@ -0,0 +1,40 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class GaborNoiseTest { + @Test + public void wavePacketsHaveDirectionalDetailAndQuietGaps() { + GaborNoise noise = new GaborNoise(1337L); + double xx = 0D; + double xz = 0D; + double zz = 0D; + int quiet = 0; + int dark = 0; + int light = 0; + for (int row = 0; row < 128; row++) { + for (int column = 0; column < 128; column++) { + double x = column * 0.0625D - 4D; + double z = row * 0.0625D - 4D; + double value = noise.noise(x, z); + quiet += Math.abs(value - 0.5D) < 0.03D ? 1 : 0; + dark += value < 0.3D ? 1 : 0; + light += value > 0.7D ? 1 : 0; + double dx = noise.noise(x + 0.001D, z) - noise.noise(x - 0.001D, z); + double dz = noise.noise(x, z + 0.001D) - noise.noise(x, z - 0.001D); + xx += dx * dx; + xz += dx * dz; + zz += dz * dz; + } + } + double spread = Math.sqrt((xx - zz) * (xx - zz) + 4D * xz * xz); + double major = xx + zz + spread; + double minor = xx + zz - spread; + assertTrue(major > minor * 3D); + assertTrue(quiet > 1000); + assertTrue(dark > 500); + assertTrue(light > 500); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/KaleidoscopeNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/KaleidoscopeNoiseTest.java new file mode 100644 index 000000000..68683c3c1 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/KaleidoscopeNoiseTest.java @@ -0,0 +1,92 @@ +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 KaleidoscopeNoiseTest { + @Test + public void wedgesMirrorAndRepeatAroundTheTileCenter() { + KaleidoscopeNoise noise = new KaleidoscopeNoise(0L); + for (int index = 0; index < 128; index++) { + double angle = index * 0.037D; + double radius = 0.05D + index * (0.4D / 128D); + double x = radius * Math.cos(angle); + double z = radius * Math.sin(angle); + double value = noise.noise(0.5D + x, z); + assertEquals(value, noise.noise(0.5D + x, -z), 1.0E-12D); + assertEquals(value, noise.noise(0.5D - z, x), 1.0E-12D); + } + } + + @Test + public void compactMotifsMeetContinuouslyAtTileAndWedgeBoundaries() { + double epsilon = 1.0E-8D; + for (long seed : new long[]{0L, 1337L, Long.MIN_VALUE}) { + KaleidoscopeNoise noise = new KaleidoscopeNoise(seed); + for (int index = 0; index < 256; index++) { + double position = index * 0.027D - 3D; + double height = index * 0.017D - 2D; + assertEquals(noise.noise(-epsilon, height, position), + noise.noise(epsilon, height, position), 1.0E-6D); + assertEquals(noise.noise(position, height, 0.5D - epsilon), + noise.noise(position, height, 0.5D + epsilon), 1.0E-6D); + assertEquals(noise.noise(0.5D - epsilon, height, 0D), + noise.noise(0.5D + epsilon, height, 0D), 1.0E-5D); + } + } + KaleidoscopeNoise noise = new KaleidoscopeNoise(0L); + for (double boundary : new double[]{-Math.PI, -Math.PI * 0.75D, 0D, Math.PI * 0.25D, Math.PI}) { + assertEquals(noise.noise(0.5D + 0.3D * Math.cos(boundary - epsilon), + 0.3D * Math.sin(boundary - epsilon)), + noise.noise(0.5D + 0.3D * Math.cos(boundary + epsilon), + 0.3D * Math.sin(boundary + epsilon)), 1.0E-5D); + } + } + + @Test + public void heightAndUpperSeedBitsChangeTheMotifs() { + KaleidoscopeNoise first = new KaleidoscopeNoise(1337L); + KaleidoscopeNoise second = new KaleidoscopeNoise(1337L + (1L << 32)); + double heightVariation = 0D; + double seedVariation = 0D; + double maximum = 0D; + for (int index = 0; index < 512; index++) { + double x = index * 0.037D - 5D; + double z = index * -0.023D + 3D; + double value = first.noise(x, z); + assertEquals(value, first.noise(x, 0D, z), 0D); + assertEquals(value, first.noise(x, 1.0E-8D, z), 1.0E-5D); + heightVariation += Math.abs(value - first.noise(x, 1.25D, z)); + seedVariation += Math.abs(value - second.noise(x, z)); + maximum = Math.max(maximum, value); + } + assertTrue(heightVariation > 10D); + assertTrue(seedVariation > 10D); + assertTrue(maximum > 0.7D); + double oneDimensionalVariation = 0D; + for (int index = 0; index < 256; index++) { + oneDimensionalVariation += Math.abs(first.noise(index * 0.013D) + - second.noise(index * 0.013D)); + } + assertTrue(oneDimensionalVariation > 10D); + } + + @Test + public void octaveSamplesStayBoundedAndRepeatableAtWorldBorders() { + KaleidoscopeNoise first = new KaleidoscopeNoise(Long.MAX_VALUE); + KaleidoscopeNoise second = new KaleidoscopeNoise(Long.MAX_VALUE); + for (int octaves : new int[]{1, 3, 16}) { + first.setOctaves(octaves); + second.setOctaves(octaves); + for (int index = 0; index < 128; index++) { + double x = -468750D + index * (937500D / 127D); + double value = first.noise(x, x * 0.37D, x * -0.71D); + assertTrue(Double.isFinite(value)); + assertTrue(value >= 0D && value <= 1D); + assertEquals(value, second.noise(x, x * 0.37D, x * -0.71D), 0D); + } + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/MarbleNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/MarbleNoiseTest.java new file mode 100644 index 000000000..295592d31 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/MarbleNoiseTest.java @@ -0,0 +1,29 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class MarbleNoiseTest { + @Test + public void narrowVeinsCrossPredominantlySoftStone() { + MarbleNoise noise = new MarbleNoise(1337L); + int dark = 0; + int soft = 0; + double minimum = 1D; + double maximum = 0D; + for (int row = 0; row < 128; row++) { + for (int column = 0; column < 128; column++) { + double value = noise.noise(column * 0.0625D - 4D, row * 0.0625D - 4D); + dark += value < 0.3D ? 1 : 0; + soft += value > 0.65D ? 1 : 0; + minimum = Math.min(minimum, value); + maximum = Math.max(maximum, value); + } + } + assertTrue(minimum < 0.15D); + assertTrue(maximum > 0.85D); + assertTrue(dark > 250 && dark < 2500); + assertTrue(soft > 10000); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/MaterialPatternNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/MaterialPatternNoiseTest.java new file mode 100644 index 000000000..046fcb515 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/MaterialPatternNoiseTest.java @@ -0,0 +1,114 @@ +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 MaterialPatternNoiseTest { + @Test + public void materialPatternsStayBoundedSeededAndVolumetric() { + for (long seed : new long[]{0L, 813753L, Long.MIN_VALUE, Long.MAX_VALUE}) { + PatternNoise[] patterns = patterns(seed); + PatternNoise[] repeated = patterns(seed); + PatternNoise[] otherSeed = patterns(seed ^ (1L << 47)); + for (int pattern = 0; pattern < patterns.length; pattern++) { + PatternNoise noise = patterns[pattern]; + double seedDifference = 0D; + double heightDifference = 0D; + for (int octaves : new int[]{1, 4, 16}) { + noise.setOctaves(octaves); + repeated[pattern].setOctaves(octaves); + otherSeed[pattern].setOctaves(octaves); + for (double origin : new double[]{-468750D, 0D, 468750D}) { + for (int i = 0; i < 128; i++) { + double x = origin + i * 0.173D; + double y = i * 0.071D - 4D; + double z = -origin + i * 0.319D; + double value = noise.noise(x, y, z); + assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D); + assertEquals(value, repeated[pattern].noise(x, y, z), 0D); + assertEquals(noise.noise(x, z), noise.noise(x, 0D, z), 0D); + seedDifference += Math.abs(value - otherSeed[pattern].noise(x, y, z)); + heightDifference += Math.abs(value - noise.noise(x, y + 0.713D, z)); + } + } + } + assertTrue(noise.getClass().getSimpleName(), seedDifference > 10D); + assertTrue(noise.getClass().getSimpleName(), heightDifference > 10D); + } + } + } + + @Test + public void materialPatternsHaveNoCoordinateBoundaryJumps() { + for (long seed : new long[]{0L, 813753L, Long.MAX_VALUE}) { + for (PatternNoise noise : patterns(seed)) { + for (double boundary : new double[]{-468750D, -2D, 0D, 3D, 468750D}) { + for (int i = 0; i < 128; i++) { + double coordinate = i * 0.073D - 4D; + assertEquals(noise.noise(boundary - 1E-7D, 0.317D, coordinate), + noise.noise(boundary + 1E-7D, 0.317D, coordinate), 1E-4D); + assertEquals(noise.noise(coordinate, 0.317D, boundary - 1E-7D), + noise.noise(coordinate, 0.317D, boundary + 1E-7D), 1E-4D); + } + } + } + } + } + + @Test + public void duneWindwardSlopeIsLongerAndHornsBendDownwind() { + DuneNoise dune = new DuneNoise(0L); + assertTrue(dune.noise(0.02D, 0.2D) > dune.noise(0.38D, 0.2D) + 0.2D); + double across = 0.2D + 0.32D * 0.65D; + double crest = 0.2D + 0.43D * 0.65D * 0.65D; + assertTrue(dune.noise(crest, across) > dune.noise(0.2D, across) + 0.05D); + assertTrue(dune.noise(0.2D, 0.2D) > dune.noise(crest, across)); + } + + @Test + public void woodKnotsCarryClosedGrowthRings() { + WoodNoise wood = new WoodNoise(0L); + double minimum = 1D; + double maximum = 0D; + for (int i = 0; i < 64; i++) { + double angle = i * Math.PI / 32D; + double across = 0.3D + Math.cos(angle) * 0.12D; + double along = 0.3D + Math.sin(angle) * 0.12D; + double x = across - 0.11D * Math.sin(along * 0.85D); + double value = wood.noise(x, along / 0.45D); + minimum = Math.min(minimum, value); + maximum = Math.max(maximum, value); + } + double brightRingX = 0.34D - 0.11D * Math.sin(0.3D * 0.85D); + assertTrue(maximum - minimum < 0.22D); + assertTrue(wood.noise(brightRingX, 0.3D / 0.45D) > maximum + 0.4D); + } + + @Test + public void strataContainsRepeatedHighAndLowBands() { + StrataNoise strata = new StrataNoise(813753L); + int highBands = 0; + int lowBands = 0; + double previous = strata.noise(0D, -8D); + double current = strata.noise(0D, -8D + 0.008D); + for (int i = 2; i < 2048; i++) { + double next = strata.noise(0D, -8D + i * 0.008D); + if (current > previous && current > next && current > 0.75D) { + highBands++; + } + if (current < previous && current < next && current < 0.25D) { + lowBands++; + } + previous = current; + current = next; + } + assertTrue(highBands >= 6); + assertTrue(lowBands >= 6); + } + + private static PatternNoise[] patterns(long seed) { + return new PatternNoise[]{new DuneNoise(seed), new StrataNoise(seed), new WoodNoise(seed)}; + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/MengerSpongeNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/MengerSpongeNoiseTest.java new file mode 100644 index 000000000..86f6a634d --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/MengerSpongeNoiseTest.java @@ -0,0 +1,83 @@ +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 MengerSpongeNoiseTest { + @Test + public void threeSubdivisionsRetainExactlyTwentyCubedCells() { + MengerSpongeNoise noise = new MengerSpongeNoise(0L); + int retained = 0; + for (int x = 0; x < 27; x++) { + for (int y = 0; y < 27; y++) { + for (int z = 0; z < 27; z++) { + retained += (int) noise.noise((x + 0.5D) / 27D, + (y + 0.5D) / 27D, (z + 0.5D) / 27D); + } + } + } + assertEquals(8000, retained); + } + + @Test + public void faceSliceContainsThreeLevelsOfSquareHoles() { + MengerSpongeNoise noise = new MengerSpongeNoise(0L); + assertEquals(0D, noise.noise(0.5D, 0D, 0.5D), 0D); + assertEquals(0D, noise.noise(1D / 6D, 0D, 1D / 6D), 0D); + assertEquals(0D, noise.noise(1D / 18D, 0D, 1D / 18D), 0D); + assertEquals(1D, noise.noise(1D / 54D, 0D, 1D / 54D), 0D); + assertEquals(0D, noise.noise(0.5D, 0.5D, 0.05D), 0D); + assertEquals(0D, noise.noise(0.05D, 0.5D, 0.5D), 0D); + int retained = 0; + for (int x = 0; x < 27; x++) { + for (int z = 0; z < 27; z++) { + retained += (int) noise.noise((x + 0.5D) / 27D, (z + 0.5D) / 27D); + } + } + assertEquals(512, retained); + } + + @Test + public void negativeTilesRepeatAndAllAxesAndUpperSeedBitsAffectGeometry() { + MengerSpongeNoise first = new MengerSpongeNoise(1337L); + MengerSpongeNoise second = new MengerSpongeNoise(1337L + (1L << 32)); + double xVariation = 0D; + double yVariation = 0D; + double zVariation = 0D; + double seedVariation = 0D; + for (int index = 0; index < 512; index++) { + double x = index * 0.019D - 4D; + double z = index * -0.031D + 2D; + double value = first.noise(x, z); + assertEquals(value, first.noise(x, 0D, z), 0D); + assertEquals(value, first.noise(x - 2D, 0D, z + 3D), 0D); + xVariation += Math.abs(value - first.noise(x + 0.137D, z)); + yVariation += Math.abs(value - first.noise(x, 0.419D, z)); + zVariation += Math.abs(value - first.noise(x, z + 0.237D)); + seedVariation += Math.abs(value - second.noise(x, z)); + } + assertTrue(xVariation > 20D); + assertTrue(yVariation > 20D); + assertTrue(zVariation > 20D); + assertTrue(seedVariation > 20D); + } + + @Test + public void octaveSamplesStayBoundedAndRepeatableAtWorldBorders() { + MengerSpongeNoise first = new MengerSpongeNoise(Long.MAX_VALUE); + MengerSpongeNoise second = new MengerSpongeNoise(Long.MAX_VALUE); + for (int octaves : new int[]{1, 3, 16}) { + first.setOctaves(octaves); + second.setOctaves(octaves); + for (int index = 0; index < 128; index++) { + double x = -468750D + index * (937500D / 127D); + double value = first.noise(x, x * 0.37D, x * -0.71D); + assertTrue(Double.isFinite(value)); + assertTrue(value >= 0D && value <= 1D); + assertEquals(value, second.noise(x, x * 0.37D, x * -0.71D), 0D); + } + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java index e2e88dfbb..f6aee8719 100644 --- a/core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java +++ b/core/src/test/java/art/arcane/iris/util/project/noise/PatternNoiseTest.java @@ -12,7 +12,9 @@ import static org.junit.Assert.assertTrue; public class PatternNoiseTest { private static final NoiseStyle[] STYLES = { NoiseStyle.GYROID, NoiseStyle.QUASICRYSTAL, NoiseStyle.TRUCHET, - NoiseStyle.CRATER, NoiseStyle.VORTEX + NoiseStyle.CRATER, NoiseStyle.VORTEX, + NoiseStyle.DUNE, NoiseStyle.STRATA, NoiseStyle.WOOD, NoiseStyle.GABOR, NoiseStyle.MARBLE, + NoiseStyle.SCALES, NoiseStyle.CHLADNI, NoiseStyle.KALEIDOSCOPE, NoiseStyle.MENGER_SPONGE, NoiseStyle.CIRCUIT }; @Test @@ -100,7 +102,9 @@ public class PatternNoiseTest { private static PatternNoise[] patterns(long seed) { return new PatternNoise[]{new GyroidNoise(seed), new QuasicrystalNoise(seed), - new TruchetNoise(seed), new CraterNoise(seed), new VortexNoise(seed)}; + new TruchetNoise(seed), new CraterNoise(seed), new VortexNoise(seed), + new DuneNoise(seed), new StrataNoise(seed), new WoodNoise(seed), new GaborNoise(seed), new MarbleNoise(seed), + new ScalesNoise(seed), new ChladniNoise(seed), new KaleidoscopeNoise(seed), new MengerSpongeNoise(seed), new CircuitNoise(seed)}; } private static void assertUnit(PatternNoise noise, double value) { diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/ScalesNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/ScalesNoiseTest.java new file mode 100644 index 000000000..e2c34bac2 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/ScalesNoiseTest.java @@ -0,0 +1,38 @@ +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 ScalesNoiseTest { + private static final double ZERO_SEED_PHASE_Z = 0.8833108082136426D; + + @Test + public void groovesTraceStaggeredSemicirclesAroundRaisedFaces() { + ScalesNoise noise = new ScalesNoise(0L); + for (int row = -3; row <= 3; row++) { + double x = (row & 1) * 0.5D; + double z = row * 0.5D - ZERO_SEED_PHASE_Z; + assertTrue(noise.noise(x, z + 0.2D) > 0.75D); + for (int point = 1; point < 12; point++) { + double angle = point * Math.PI / 12D; + assertTrue(noise.noise(x + Math.cos(angle) * 0.5D, z + Math.sin(angle) * 0.5D) < 0.24D); + } + } + } + + @Test + public void staggeredRowAndColumnBoundariesRemainContinuous() { + ScalesNoise noise = new ScalesNoise(0L); + for (int row = -4; row <= 4; row++) { + double z = row * 0.5D - ZERO_SEED_PHASE_Z; + for (int point = 0; point < 512; point++) { + double x = point / 128D - 2D; + assertEquals(noise.noise(x, z - 1.0E-8D), noise.noise(x, z + 1.0E-8D), 1.0E-5D); + assertEquals(noise.noise(row * 0.5D - 1.0E-8D, x), + noise.noise(row * 0.5D + 1.0E-8D, x), 1.0E-5D); + } + } + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/TextureNoiseTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/TextureNoiseTest.java new file mode 100644 index 000000000..6eeb91365 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/noise/TextureNoiseTest.java @@ -0,0 +1,105 @@ +package art.arcane.iris.util.project.noise; + +import org.junit.Test; + +import java.util.SplittableRandom; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TextureNoiseTest { + @Test + public void texturesKeepUnitRangeAndExactHorizontalSlicesThroughSixteenOctaves() { + SplittableRandom random = new SplittableRandom(715L); + for (long seed : new long[]{0L, 1337L, Long.MIN_VALUE, Long.MAX_VALUE}) { + for (PatternNoise noise : textures(seed)) { + for (int octaves : new int[]{1, 4, 16}) { + noise.setOctaves(octaves); + for (int index = 0; index < 128; index++) { + double x = random.nextDouble(-468750D, 468750D); + double y = random.nextDouble(-32D, 32D); + double z = random.nextDouble(-468750D, 468750D); + double value = noise.noise(x, y, z); + assertTrue(noise.getClass().getSimpleName(), Double.isFinite(value) && value >= 0D && value <= 1D); + assertEquals(noise.noise(x, z), noise.noise(x, 0D, z), 0D); + assertEquals(noise.noise(x), noise.noise(x, 0D, 0D), 0D); + } + } + } + } + } + + @Test + public void texturesUseTheFullSeedAndEveryCoordinate() { + PatternNoise[] first = textures(1337L); + PatternNoise[] same = textures(1337L); + PatternNoise[] highSeed = textures(1337L + (1L << 32)); + for (int style = 0; style < first.length; style++) { + double seedDifference = 0D; + double xDifference = 0D; + double yDifference = 0D; + double zDifference = 0D; + for (int index = 0; index < 256; index++) { + double x = index * 0.073D - 4D; + double y = index * 0.011D - 1D; + double z = index * -0.113D + 3D; + double value = first[style].noise(x, y, z); + assertEquals(value, same[style].noise(x, y, z), 0D); + seedDifference += Math.abs(value - highSeed[style].noise(x, y, z)); + xDifference += Math.abs(value - first[style].noise(x + 0.31D, y, z)); + yDifference += Math.abs(value - first[style].noise(x, y + 0.37D, z)); + zDifference += Math.abs(value - first[style].noise(x, y, z + 0.41D)); + } + assertTrue(first[style].getClass().getSimpleName(), seedDifference > 4D); + assertTrue(first[style].getClass().getSimpleName(), xDifference > 4D); + assertTrue(first[style].getClass().getSimpleName(), yDifference > 4D); + assertTrue(first[style].getClass().getSimpleName(), zDifference > 4D); + } + } + + @Test + public void octaveChangesAddDetailAndRestoreTheOriginalSamples() { + for (PatternNoise noise : textures(701L)) { + double[] original = new double[128]; + for (int index = 0; index < original.length; index++) { + original[index] = noise.noise(index * 0.071D, 0.193D, index * -0.113D); + } + noise.setOctaves(4); + double difference = 0D; + for (int index = 0; index < original.length; index++) { + difference += Math.abs(original[index] - noise.noise(index * 0.071D, 0.193D, index * -0.113D)); + } + assertTrue(noise.getClass().getSimpleName(), difference > 1D); + noise.setOctaves(0); + for (int index = 0; index < original.length; index++) { + assertEquals(original[index], noise.noise(index * 0.071D, 0.193D, index * -0.113D), 0D); + } + noise.setOctaves(16); + double maximum = noise.noise(0.173D, 0.193D, -0.391D); + noise.setOctaves(Integer.MAX_VALUE); + assertEquals(maximum, noise.noise(0.173D, 0.193D, -0.391D), 0D); + } + } + + @Test + public void textureSamplingHasNoIntegerCellSeams() { + for (PatternNoise noise : textures(913L)) { + for (int boundary = -4; boundary <= 4; boundary++) { + for (int index = 0; index < 64; index++) { + double first = index * 0.073D - 2D; + double second = index * -0.113D + 3D; + assertEquals(noise.noise(boundary - 1.0E-8D, first, second), + noise.noise(boundary + 1.0E-8D, first, second), 1.0E-5D); + assertEquals(noise.noise(first, boundary - 1.0E-8D, second), + noise.noise(first, boundary + 1.0E-8D, second), 1.0E-5D); + assertEquals(noise.noise(first, second, boundary - 1.0E-8D), + noise.noise(first, second, boundary + 1.0E-8D), 1.0E-5D); + } + } + } + } + + private static PatternNoise[] textures(long seed) { + return new PatternNoise[]{new GaborNoise(seed), new MarbleNoise(seed), new ScalesNoise(seed)}; + } +}