mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 08:07:42 +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
@@ -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