Klasse für Perlin Noise implementiert

Die Klasse erzeugt Zuffalswerte nach dem von Ken Perlin erfundenen „Improved Perlin Noise“ Algorithmus.
This commit is contained in:
ngb
2022-07-14 17:59:17 +02:00
parent 949f058b5d
commit 0098621ebe
3 changed files with 308 additions and 246 deletions

View File

@@ -1327,7 +1327,7 @@ public class Constants {
* @return Ein zufälliger Wert. * @return Ein zufälliger Wert.
*/ */
public static final double noise() { public static final double noise() {
return getNoise().noise(N++); return getNoise().noise(0.005 * N++);
} }
/** /**

View File

@@ -5,304 +5,362 @@ import java.util.Random;
/** /**
* Zufallsgenerator für Perlin-Noise. * Zufallsgenerator für Perlin-Noise.
* <p> * <p>
* Die Implementierung basiert auf dem von Ken perlin entwickelten Algorithmus * Die Implementierung basiert auf dem von Ken Perlin entwickelten Algorithmus
* und wurde von Matthew A. Johnston für Java implementiert. * und wurde anhand der <a
* <p> * href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
* Original KOmmentar: * FLAFLA2</a> implementiert.
* <pre>
* This is Ken Perlin's implementation of Perlin Noise but modified to be more
* OOP.
* </pre>
*
* @author Ken Perlin, Matthew A. Johnston (WarmWaffles)
*/ */
public class Noise { public class Noise {
private Random rand; private static final int N = 256;
private static final int P = 8; private static final int M = N - 1;
private static final int B = 1 << P;
private static final int M = B - 1;
private static final int NP = 8;
private static final int N = 1 << NP;
/**
* Interne Permutationstabelle für diesen Generator
*/
private int[] p; private int[] p;
private double[][] g2; private double octaves = 1, persistence = .5;
private double[] g1; private double frequency = 1;
private static double[][] points; private double amplitude = 1;
private int repeat = -1;
private double rangeMin = 0.0, rangeMax = 1.0;
public Noise() { public Noise() {
this(System.nanoTime()); this(null);
} }
public Noise( long seed ) { public Noise( long seed ) {
this(new Random(seed)); init(new Random(seed));
}
public Noise( Random rand ) {
p = new int[B + B + 2];
g2 = new double[B + B + 2][2];
g1 = new double[B + B + 2];
points = new double[32][3];
this.rand = rand;
init();
} }
/** /**
* A stub function that calls {@link #init()} * Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
* *
* @param seed * @param rand
*/ */
public void setSeed( int seed ) { public Noise( Random rand ) {
this.rand = new Random(seed); init(rand);
init();
} }
public void setRandom( Random rand ) { public double getOctaves() {
this.rand = rand; return octaves;
init();
} }
public double noise( double x, double y, double z ) { public void setOctaves( double pOctaves ) {
int bx, by, bz, b0, b1, b00, b10, b01, b11; this.octaves = pOctaves;
double rx0, rx1, ry0, ry1, rz, sx, sy, sz, a, b, c, d, u, v, q[];
bx = (int) Math.IEEEremainder(Math.floor(x), B);
if( bx < 0 )
bx += B;
rx0 = x - Math.floor(x);
rx1 = rx0 - 1;
by = (int) Math.IEEEremainder(Math.floor(y), B);
if( by < 0 )
by += B;
ry0 = y - Math.floor(y);
ry1 = ry0 - 1;
bz = (int) Math.IEEEremainder(Math.floor(z), B);
if( bz < 0 )
bz += B;
rz = z - Math.floor(z);
/*
if (bx < 0 || bx >= B + B + 2)
System.out.println(bx);
*/
b0 = p[bx];
bx++;
b1 = p[bx];
b00 = p[b0 + by];
b10 = p[b1 + by];
by++;
b01 = p[b0 + by];
b11 = p[b1 + by];
sx = s_curve(rx0);
sy = s_curve(ry0);
sz = s_curve(rz);
q = G(b00 + bz);
u = rx0 * q[0] + ry0 * q[1] + rz * q[2];
q = G(b10 + bz);
v = rx1 * q[0] + ry0 * q[1] + rz * q[2];
a = lerp(sx, u, v);
q = G(b01 + bz);
u = rx0 * q[0] + ry1 * q[1] + rz * q[2];
q = G(b11 + bz);
v = rx1 * q[0] + ry1 * q[1] + rz * q[2];
b = lerp(sx, u, v);
c = lerp(sy, a, b);
bz++;
rz--;
q = G(b00 + bz);
u = rx0 * q[0] + ry0 * q[1] + rz * q[2];
q = G(b10 + bz);
v = rx1 * q[0] + ry0 * q[1] + rz * q[2];
a = lerp(sx, u, v);
q = G(b01 + bz);
u = rx0 * q[0] + ry1 * q[1] + rz * q[2];
q = G(b11 + bz);
v = rx1 * q[0] + ry1 * q[1] + rz * q[2];
b = lerp(sx, u, v);
d = lerp(sy, a, b);
return lerp(sz, c, d);
} }
public double noise( double x, double y ) { public double getPersistence() {
int bx0, bx1, by0, by1, b00, b10, b01, b11; return persistence;
double rx0, rx1, ry0, ry1, sx, sy, a, b, t, u, v, q[]; }
int i, j;
t = x + N; public void setPersistence( double pPersistence ) {
bx0 = ((int) t) & M; this.persistence = pPersistence;
bx1 = (bx0 + 1) & M; }
rx0 = t - (int) t;
rx1 = rx0 - 1;
t = y + N; public double getFrequency() {
by0 = ((int) t) & M; return frequency;
by1 = (by0 + 1) & M; }
ry0 = t - (int) t;
ry1 = ry0 - 1;
i = p[bx0]; public void setFrequency( double pFrequency ) {
j = p[bx1]; this.frequency = pFrequency;
}
b00 = p[i + by0]; public double getAmplitude() {
b10 = p[j + by0]; return amplitude;
b01 = p[i + by1]; }
b11 = p[j + by1];
sx = s_curve(rx0); public void setAmplitude( double pAmplitude ) {
sy = s_curve(ry0); this.amplitude = pAmplitude;
}
q = g2[b00]; public void setRange( double pRangeMin, double pRangeMax ) {
u = rx0 * q[0] + ry0 * q[1]; this.rangeMin = pRangeMin;
q = g2[b10]; this.rangeMax = pRangeMax;
v = rx1 * q[0] + ry0 * q[1]; }
a = lerp(sx, u, v);
q = g2[b01]; public double getRangeMin() {
u = rx0 * q[0] + ry1 * q[1]; return rangeMin;
q = g2[b11]; }
v = rx1 * q[0] + ry1 * q[1];
b = lerp(sx, u, v);
return lerp(sy, a, b); public double getRangeMax() {
return rangeMax;
}
public int getRepeat() {
return repeat;
}
public void setRepeat( int pRepeat ) {
this.repeat = pRepeat;
} }
public double noise( double x ) { public double noise( double x ) {
double total = 0;
double freq = this.frequency;
double amp = this.amplitude;
double maxValue = 0;
int bx0, bx1; for( int i = 0; i < octaves; i++ ) {
double rx0, rx1, sx, t, u, v; total += perlin(x * freq) * amp;
t = x + N;
bx0 = ((int) t) & M;
bx1 = (bx0 + 1) & M;
rx0 = t - (int) t;
rx1 = rx0 - 1;
sx = s_curve(rx0); maxValue += amp;
u = rx0 * g1[p[bx0]];
v = rx1 * g1[p[bx1]];
return lerp(sx, u, v); amp *= persistence;
freq *= 2;
} }
// ======================================================================== return lerp(rangeMin, rangeMax, (total / maxValue));
// PRIVATE
// ========================================================================
private void normalize2( double v[] ) {
double s;
s = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
v[0] = v[0] / s;
v[1] = v[1] / s;
} }
private double[] G( int i ) { public double noise( double x, double y ) {
return points[i % 32]; double total = 0;
double freq = this.frequency;
double amp = this.amplitude;
double maxValue = 0;
for( int i = 0; i < octaves; i++ ) {
total += perlin(x * freq, y * freq) * amp;
maxValue += amp;
amp *= persistence;
freq *= 2;
} }
private double s_curve( double t ) { return lerp(rangeMin, rangeMax, (total / maxValue));
return t * t * (3 - t - t);
} }
private double lerp( double t, double a, double b ) { public double noise( double x, double y, double z ) {
double total = 0;
double freq = this.frequency;
double amp = this.amplitude;
double maxValue = 0;
for( int i = 0; i < octaves; i++ ) {
total += perlin(x * freq, y * freq, z * freq) * amp;
maxValue += amp;
amp *= persistence;
freq *= 2;
}
return lerp(rangeMin, rangeMax, (total / maxValue));
}
private double perlin( double x ) {
// @formatter:off
if( repeat > 0 ) {
x %= repeat;
}
int xi = (int)x & M;
double xf = x - (int)x;
double u = fade(xf);
int a, b;
a = p[ xi ];
b = p[inc(xi)];
return (lerp(grad(a,xf), grad(b,xf-1), u) + 1) / 2;
// @formatter:on
}
private double perlin( double x, double y ) {
// @formatter:off
if( repeat > 0 ) {
x %= repeat;
y %= repeat;
}
int xi = (int) x & M;
int yi = (int) y & M;
double xf = x - (int) x;
double yf = y - (int) y;
double u = fade(xf);
double v = fade(yf);
int aa, ab, ba, bb;
aa = p[p[ xi ] + yi ];
ab = p[p[ xi ] + inc(yi)];
ba = p[p[inc(xi)] + yi ];
bb = p[p[inc(xi)] + inc(yi)];
double x1, x2;
x1 = lerp(
grad(aa, xf , yf),
grad(ba, xf-1, yf),
u);
x2 = lerp(
grad(ab, xf , yf-1),
grad(bb, xf-1, yf-1),
u);
return (lerp(x1, x2, v) + 1) / 2;
// @formatter:on
}
private double perlin( double x, double y, double z ) {
// @formatter:off
if( repeat > 0 ) {
x %= repeat;
y %= repeat;
z %= repeat;
}
int xi = (int)x & M;
int yi = (int)y & M;
int zi = (int)z & M;
double xf = x - (int)x;
double yf = y - (int)y;
double zf = z - (int)z;
double u = fade(xf);
double v = fade(yf);
double w = fade(zf);
int aaa, aba, aab, abb, baa, bba, bab, bbb;
aaa = p[p[p[ xi ] + yi ] + zi ];
aba = p[p[p[ xi ] + inc(yi)] + zi ];
aab = p[p[p[ xi ] + yi ] + inc(zi)];
abb = p[p[p[ xi ] + inc(yi)] + inc(zi)];
baa = p[p[p[inc(xi)] + yi ] + zi ];
bba = p[p[p[inc(xi)] + inc(yi)] + zi ];
bab = p[p[p[inc(xi)] + yi ] + inc(zi)];
bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)];
double x1, x2, y1, y2;
x1 = lerp(
grad(aaa, xf , yf, zf),
grad(baa, xf-1, yf, zf),
u);
x2 = lerp(
grad(aba, xf , yf-1, zf),
grad(bba, xf-1, yf-1, zf),
u);
y1 = lerp(x1, x2, v);
x1 = lerp(
grad(aab, xf , yf, zf-1),
grad(bab, xf-1, yf, zf-1),
u);
x2 = lerp(
grad(abb, xf , yf-1, zf-1),
grad(bbb, xf-1, yf-1, zf-1),
u);
y2 = lerp(x1, x2, v);
return (lerp(y1, y2, w) + 1) / 2;
// @formatter:on
}
public void init( Random rand ) {
p = new int[N * 2];
if( rand == null ) {
System.arraycopy(PERLIN_PERMUTATION, 0, p, 0, N);
} else {
// Generate random permutation
for( int i = 0; i < N; i++ ) {
int n = rand.nextInt(N);
if( p[n] == 0 )
p[n] = i;
else
i--;
}
}
// Duplicate permutation array to prevent overflow errors
System.arraycopy(p, 0, p, N, N);
}
private double fade( double t ) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
private double lerp( double a, double b, double t ) {
return a + t * (b - a); return a + t * (b - a);
} }
private final void init() { private int inc( int i ) {
int i, j, k; ++i;
double u, v, w, U, V, W, Hi, Lo; if( repeat > 0 )
i = i % repeat;
for( i = 0; i < B; i++ ) { return i;
p[i] = i;
g1[i] = 2 * rand.nextDouble() - 1;
do {
u = 2 * rand.nextDouble() - 1;
v = 2 * rand.nextDouble() - 1;
} while( u * u + v * v > 1 ||
Math.abs(u) > 2.5 * Math.abs(v) ||
Math.abs(v) > 2.5 * Math.abs(u) ||
Math.abs(Math.abs(u) - Math.abs(v)) < .4 );
g2[i][0] = u;
g2[i][1] = v;
normalize2(g2[i]);
do {
u = 2 * rand.nextDouble() - 1;
v = 2 * rand.nextDouble() - 1;
w = 2 * rand.nextDouble() - 1;
U = Math.abs(u);
V = Math.abs(v);
W = Math.abs(w);
Lo = Math.min(U, Math.min(V, W));
Hi = Math.max(U, Math.max(V, W));
} while( u * u + v * v + w * w > 1
|| Hi > 4 * Lo
|| Math.min(Math.abs(U - V),
Math.min(Math.abs(U - W), Math.abs(V - W))) < .2 );
} }
while( --i > 0 ) { private double grad( int hash, double x ) {
k = p[i]; switch( hash & 0x1 ) {
j = (int) (rand.nextLong() & M); // @formatter:off
p[i] = p[j]; case 0x0: return x;
p[j] = k; case 0x1: return -x;
default: return 0;
// @formatter:on
} }
for( i = 0; i < B + 2; i++ ) {
p[B + i] = p[i];
g1[B + i] = g1[i];
for( j = 0; j < 2; j++ )
g2[B + i][j] = g2[i][j];
} }
points[3][0] = points[3][1] = points[3][2] = Math.sqrt(1. / 3);
double r2 = Math.sqrt(1. / 2);
double s = Math.sqrt(2 + r2 + r2);
for( i = 0; i < 3; i++ ) private double grad( int hash, double x, double y ) {
for( j = 0; j < 3; j++ ) switch( hash & 0x3 ) {
points[i][j] = (i == j ? 1 + r2 + r2 : r2) / s; // @formatter:off
case 0x0: return x;
case 0x1: return -x;
case 0x2: return y;
case 0x3: return -y;
default: return 0;
// @formatter:on
}
}
private double grad( int hash, double x, double y, double z ) {
switch( hash & 0xF ) {
// @formatter:off
case 0x0: return x + y;
case 0x1: return -x + y;
case 0x2: return x - y;
case 0x3: return -x - y;
case 0x4: return x + z;
case 0x5: return -x + z;
case 0x6: return x - z;
case 0x7: return -x - z;
case 0x8: return y + z;
case 0x9: return -y + z;
case 0xA: return y - z;
case 0xB: return -y - z;
case 0xC: return y + x;
case 0xD: return -y + z;
case 0xE: return y - x;
case 0xF: return -y - z;
default: return 0; // never happens
// @formatter:on
}
}
for( i = 0; i <= 1; i++ ) { private static final int[] PERLIN_PERMUTATION = new int[]{
for( j = 0; j <= 1; j++ ) { 151, 160, 137, 91, 90, 15,
for( k = 0; k <= 1; k++ ) { 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
int n = i + j * 2 + k * 4; 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
if( n > 0 ) { 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
for( int m = 0; m < 4; m++ ) { 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
points[4 * n + m][0] = (i == 0 ? 1 : -1) * points[m][0]; 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
points[4 * n + m][1] = (j == 0 ? 1 : -1) * points[m][1]; 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
points[4 * n + m][2] = (k == 0 ? 1 : -1) * points[m][2]; 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
} 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
} 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
} 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
} 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
} 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
} };
} }

View File

@@ -0,0 +1,4 @@
import static org.junit.jupiter.api.Assertions.*;
class NoiseTest {
}