mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
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
This commit is contained in:
1 parent
50894b90ce
commit
645edcff5c
13 files changed
+700
-1
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user