mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user