From 73525c170df2e690261a6e644193f838aa9fe4b6 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 18 Feb 2026 23:10:49 -0500 Subject: [PATCH] Noise Pass --- .../engine/object/IrisNoiseGenerator.java | 4 +- .../arcane/iris/engine/object/NoiseStyle.java | 93 ++++++ .../arcane/iris/util/project/noise/CNG.java | 20 +- .../iris/util/project/noise/CloverNoise.java | 37 ++- .../util/project/noise/FastNoiseDouble.java | 10 +- .../util/project/noise/HexJamesNoise.java | 288 ++++++++++++++++++ .../project/noise/HexRandomSizeNoise.java | 253 +++++++++++++++ .../util/project/noise/HexSimplexNoise.java | 125 ++++++++ .../iris/util/project/noise/HexagonNoise.java | 83 +++++ .../iris/util/project/noise/NoiseType.java | 20 ++ .../noise/SierpinskiTriangleNoise.java | 98 ++++++ 11 files changed, 1012 insertions(+), 19 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/HexJamesNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/HexRandomSizeNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/HexSimplexNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/HexagonNoise.java create mode 100644 core/src/main/java/art/arcane/iris/util/project/noise/SierpinskiTriangleNoise.java 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 c49cb29b0..9beecb09d 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 @@ -100,8 +100,8 @@ public class IrisNoiseGenerator { for (IrisNoiseGenerator i : fracture) { if (i.isEnabled()) { - x += i.getNoise(superSeed + seed + g, xv, zv, data) - (opacity / 2D); - z -= i.getNoise(superSeed + seed + g, zv, xv, data) - (opacity / 2D); + x += i.getNoise(superSeed + seed + g, xv, zv, data) - (i.getOpacity() / 2D); + z += i.getNoise(superSeed + seed + g, zv, xv, data) - (i.getOpacity() / 2D); } g += 819; } 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 b67b20dbf..1a08e9afe 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 @@ -143,6 +143,21 @@ public enum NoiseStyle { @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)), + @Desc("Hexagonal cells with stable per-cell values.") + HEXAGON(rng -> new CNG(rng, NoiseType.HEXAGON, 1D, 1).scale(1)), + + @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("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)), + + @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("Sierpinski triangle mask blended with simplex heat.") + SIERPINSKI_TRIANGLE(rng -> new CNG(rng, NoiseType.SIERPINSKI_TRIANGLE, 1D, 1).scale(1)), + @Desc("Classic German Engineering") NOWHERE(rng -> CNG.signaturePerlin(rng).scale(0.776).bake()), @@ -152,6 +167,21 @@ public enum NoiseStyle { @Desc("Classic German Engineering") NOWHERE_CLOVER(rng -> CNG.signaturePerlin(rng, NoiseType.CLOVER).scale(1).bake()), + @Desc("Classic German Engineering") + NOWHERE_HEXAGON(rng -> CNG.signaturePerlin(rng, NoiseType.HEXAGON).scale(1).bake()), + + @Desc("Classic German Engineering") + NOWHERE_HEX_JAMES(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_JAMES).scale(1).bake()), + + @Desc("Classic German Engineering") + NOWHERE_HEX_SIMPLEX(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_SIMPLEX).scale(1).bake()), + + @Desc("Classic German Engineering") + NOWHERE_HEX_RANDOM_SIZE(rng -> CNG.signaturePerlin(rng, NoiseType.HEX_RANDOM_SIZE).scale(1).bake()), + + @Desc("Classic German Engineering") + NOWHERE_SIERPINSKI_TRIANGLE(rng -> CNG.signaturePerlin(rng, NoiseType.SIERPINSKI_TRIANGLE).scale(1).bake()), + @Desc("Classic German Engineering") NOWHERE_SIMPLEX(rng -> CNG.signaturePerlin(rng, NoiseType.SIMPLEX).scale(1).bake()), @@ -242,6 +272,21 @@ public enum NoiseStyle { @Desc("FBM Fractal Iris Noise. Single octave.") FRACTAL_FBM_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.FRACTAL_FBM_SIMPLEX)), + @Desc("Fractal hexagonal cell noise.") + FRACTAL_HEXAGON(rng -> new CNG(rng, NoiseType.HEXAGON, 1D, 4).scale(1)), + + @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("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)), + + @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("Fractal Sierpinski triangle mask blended with simplex heat.") + FRACTAL_SIERPINSKI_TRIANGLE(rng -> new CNG(rng, NoiseType.SIERPINSKI_TRIANGLE, 1D, 4).scale(1)), + @Desc("Rigid Multi Fractal Simplex Noise. Single octave.") FRACTAL_RM_SIMPLEX(rng -> new CNG(rng, NoiseType.FRACTAL_RIGID_MULTI_SIMPLEX, 1D, 1)), @@ -407,6 +452,54 @@ public enum NoiseStyle { @Desc("Cubic Noise") CUBIC_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.CUBIC).scale(256)), + @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") + HEXAGON_IRIS(rng -> CNG.signature(rng, NoiseType.HEXAGON).scale(1)), + + @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") + HEXAGON_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEXAGON).scale(1)), + + @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") + HEXAGON_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEXAGON).scale(1)), + + @Desc("Hexagonal cell noise distorted using Iris styled wispy noise.") + HEXAGON_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEXAGON).scale(1)), + + @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") + HEX_JAMES_IRIS(rng -> CNG.signature(rng, NoiseType.HEX_JAMES).scale(1)), + + @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") + HEX_JAMES_IRIS_DOUBLE(rng -> CNG.signatureDouble(rng, NoiseType.HEX_JAMES).scale(1)), + + @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") + HEX_JAMES_IRIS_THICK(rng -> CNG.signatureThick(rng, NoiseType.HEX_JAMES).scale(1)), + + @Desc("Hex James substitution pattern distorted using Iris styled wispy noise.") + HEX_JAMES_IRIS_HALF(rng -> CNG.signatureHalf(rng, NoiseType.HEX_JAMES).scale(1)), + + @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)), + + @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)), + + @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)), + + @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)), + + @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)), + + @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)), + + @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)), + + @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)), + @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)), 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 e572d9a55..bc23ad9b8 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 @@ -462,10 +462,22 @@ public class CNG { return generator.noise(x * scale, y * scale, z * scale) * opacity; } - double f = fracture.noise(dim) * fscale; - double x = dim.length > 0 ? dim[0] + f : 0D; - double y = dim.length > 1 ? dim[1] - f : 0D; - double z = dim.length > 2 ? dim[2] - f : 0D; + 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; } 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 5d1caab00..1f37697f9 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 @@ -87,10 +87,20 @@ public class CloverNoise implements NoiseGenerator { return input; } + private long mix(long input) { + input = (input ^ (input >>> 33)) * 0xff51afd7ed558ccdL; + input = (input ^ (input >>> 33)) * 0xc4ceb9fe1a85ec53L; + return input ^ (input >>> 33); + } + private double hash(Vector2 position) { - long hash = doHash(seed, (long) Math.floor(position.getX())); - hash = doHash(hash, (long) Math.floor(position.getY())); - hash = doHash(hash, hash * (long) Math.floor(position.getX() + position.getY())); + long x = (long) Math.floor(position.getX()); + long y = (long) Math.floor(position.getY()); + long hash = seed; + hash ^= mix(x + 0x9E3779B97F4A7C15L); + hash ^= mix(y + 0xC2B2AE3D27D4EB4FL); + hash = mix(hash); + hash &= (HASH_M - 1); if (hash < 0) { hash += HASH_M; } @@ -392,11 +402,22 @@ public class CloverNoise implements NoiseGenerator { return input; } + private long mix(long input) { + input = (input ^ (input >>> 33)) * 0xff51afd7ed558ccdL; + input = (input ^ (input >>> 33)) * 0xc4ceb9fe1a85ec53L; + return input ^ (input >>> 33); + } + private double hash(Vector3 position) { - long hash = doHash(seed, (long) Math.floor(position.getX())); - hash = doHash(hash, (long) Math.floor(position.getY())); - hash = doHash(hash, (long) Math.floor(position.getZ())); - hash = doHash(hash, hash * (long) Math.floor(position.getX() + position.getY() + position.getZ())); + long x = (long) Math.floor(position.getX()); + long y = (long) Math.floor(position.getY()); + long z = (long) Math.floor(position.getZ()); + long hash = seed; + hash ^= mix(x + 0x9E3779B97F4A7C15L); + hash ^= mix(y + 0xC2B2AE3D27D4EB4FL); + hash ^= mix(z + 0x165667B19E3779F9L); + hash = mix(hash); + hash &= (HASH_M - 1); if (hash < 0) { hash += HASH_M; } @@ -1043,4 +1064,4 @@ public class CloverNoise implements NoiseGenerator { return new Vector3(y * c.z - z * c.y, z * c.x - x * c.z, x * c.y - y * c.x); } } -} \ 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 de0e0fe1a..34970d77b 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 @@ -37,8 +37,8 @@ public class FastNoiseDouble { private final static double F3 = 1.0 / 3.0; private final static double G3 = 1.0 / 6.0; private final static double G33 = G3 * 3 - 1; - private final static double F2 = 1.0 / 2.0; - private final static double G2 = 1.0 / 4.0; + private final static double F2 = 0.5 * (Math.sqrt(3.0) - 1.0); + private final static double G2 = (3.0 - Math.sqrt(3.0)) / 6.0; private static final byte[] SIMPLEX_4D = {0, 1, 2, 3, 0, 1, 3, 2, 0, 0, 0, 0, 0, 2, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 3, 1, 2, 0, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 0, 1, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 1, 2, 3, 1, 0, 1, 0, 2, 3, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 3, 1, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 2, 3, 0, 2, 1, 0, 0, 0, 0, 3, 1, 2, 0, 2, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 2, 0, 0, 0, 0, 3, 2, 0, 1, 3, 2, 1, 0 }; private final static double F4 = (2.23606797 - 1.0) / 4.0; @@ -1132,8 +1132,8 @@ public class FastNoiseDouble { double x1 = x0 - i1 + G2; double y1 = y0 - j1 + G2; - double x2 = x0 - 1 + F2; - double y2 = y0 - 1 + F2; + double x2 = x0 - 1 + (2 * G2); + double y2 = y0 - 1 + (2 * G2); double n0, n1, n2; @@ -1986,4 +1986,4 @@ public class FastNoiseDouble { } } -} \ No newline at end of file +} 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 new file mode 100644 index 000000000..135ab260c --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexJamesNoise.java @@ -0,0 +1,288 @@ +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; + private static final int MAX_DEPTH = 7; + private static final double HEAT_SCALE = 0.31D; + 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 final long seed; + private final SimplexNoise heatSimplex; + private int octaves; + + public HexJamesNoise(long seed) { + RNG rng = new RNG(seed); + this.seed = rng.lmax(); + this.heatSimplex = new SimplexNoise(rng.nextParallelRNG(877L).lmax()); + this.octaves = 1; + } + + private double clip(double value) { + if (value < 0D) { + return 0D; + } + + if (value > 1D) { + return 1D; + } + + return value; + } + + 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 double random01(long nodeHash, int salt) { + long mixed = nodeHash ^ (CONST_X * (salt + 1L)) ^ (CONST_Z * (salt + 7L)); + return hashToUnit(mixed); + } + + 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; + } + } + + return bestIndex; + } + + private boolean shouldContinue(int level, int childIndex, long nodeHash) { + 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) { + 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++) { + double[] centersQ = new double[7]; + double[] centersR = new double[7]; + 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; + } + } + + 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); + } +} 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 new file mode 100644 index 000000000..d6a518c0a --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexRandomSizeNoise.java @@ -0,0 +1,253 @@ +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; + 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 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 final long seed; + private final SimplexNoise heatSimplex; + private final SimplexNoise structureSimplex; + private int octaves; + + 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; + } + + private double clip(double value) { + if (value < 0D) { + return 0D; + } + + if (value > 1D) { + return 1D; + } + + return value; + } + + 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 double random01(long nodeHash, int salt) { + long mixed = nodeHash ^ (CONST_X * (salt + 1L)) ^ (CONST_Z * (salt + 31L)); + return hashToUnit(mixed); + } + + 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) { + 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; + } + + double[] centersQ = new double[7]; + double[] centersR = new double[7]; + 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); + } +} 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 new file mode 100644 index 000000000..554896d16 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexSimplexNoise.java @@ -0,0 +1,125 @@ +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 HexSimplexNoise(long seed) { + RNG rng = new RNG(seed); + this.cellHeatSimplex = new SimplexNoise(rng.nextParallelRNG(811L).lmax()); + this.octaves = 1; + } + + private double clip(double value) { + if (value < 0D) { + return 0D; + } + + if (value > 1D) { + return 1D; + } + + return value; + } + + 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); + 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); + } +} 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 new file mode 100644 index 000000000..0f3181d3d --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/HexagonNoise.java @@ -0,0 +1,83 @@ +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 HexagonNoise(long seed) { + this.seed = new RNG(seed).lmax(); + } + + private long mix(long input) { + input = (input ^ (input >>> 33)) * 0xff51afd7ed558ccdL; + input = (input ^ (input >>> 33)) * 0xc4ceb9fe1a85ec53L; + return input ^ (input >>> 33); + } + + 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); + 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; + } +} 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 cc23eb520..242b3bdaa 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 @@ -68,6 +68,26 @@ public enum NoiseType { 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)), VASCULAR(VascularNoise::new); private final NoiseFactory f; 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 new file mode 100644 index 000000000..fbf0fa6f6 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/noise/SierpinskiTriangleNoise.java @@ -0,0 +1,98 @@ +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; + + 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); + } + + @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); + } +}