I\'m investigating a few of the various implementations for coherent noise (I know there are libraries, but this is mostly for my own edification and curiosity) and how you
When you calculate dot product, you may get values outside -1 +1 range, however during interpolation step, final value falls in -1 +1 range. This is because distance vectors of dots products that are interpolated point into opposite directions of interpolated axis. During the last interpolation output will not exceed -1 +1 range.
Final output range for Perlin noise is defined by length of gradient vectors. If we talk about 2D noise and our goal to have output range -1 +1, the length of the gradient vectors should be sqrt(2) (~1,4142). It is common mistake to mix these vectors (1, 1) (-1, 1) (1, -1) (-1, -1) and (1, 0) (0, 1) (-1, 0) (0, -1). In this case final output range still will be -1 +1 range, however the values in range -0.707 +0.707 will be more frequent. To avoid this problem (1, 0) (0, 1) (-1, 0) (0, -1) vectors should be replaced with (sqrt(2), 0) (0, sqrt(2)) (-sqrt(2), 0) (0, -sqrt(2)).
You have y0 = (int)d.x;
, but you mean d.y
. This will most certainly affect your output range, and is the reason you are seeing such largely out-of-range values.
That said, the output range of Perlin noise is not actually [-1, 1]. While I'm not quite sure of the math myself (I must be getting old), this rather lengthy discussion works out that the actual range is [-sqrt(n)/2, sqrt(n)/2], where n is the dimensionality (2 in your case). So the output range of your 2D Perlin noise function should be [-0.707, 0.707]. This is somehow related to the fact that both d
and the interpolation parameters are a function of p
. If you read through that discussion, you may find the precise explanation you are looking for (particularly, post #7).
I am testing your implementation using the following program (which I hacked together from your example, so pardon the weird use of gridCells
and gridSize
):
import java.util.Random;
public class Perlin {
static final int gridSize = 200;
static final int gridCells = 20;
static final Vec[][] gradients = new Vec[gridCells + 1][gridCells + 1];
static void initializeGradient () {
Random rand = new Random();
for (int r = 0; r < gridCells + 1; ++ r) {
for (int c = 0; c < gridCells + 1; ++ c) {
double theta = rand.nextFloat() * Math.PI;
gradients[c][r] = new Vec(Math.cos(theta), Math.sin(theta));
}
}
}
static class Vec {
double x;
double y;
Vec (double x, double y) { this.x = x; this.y = y; }
double dot (Vec v) { return x * v.x + y * v.y; }
Vec sub (double x, double y) { return new Vec(this.x - x, this.y - y); }
}
static double fade (double v) {
// easing doesn't matter for range sample test.
// v = 3 * v * v - 2 * v * v * v;
return v;
}
static double lerp (double p, double a, double b) {
return (b - a) * p + a;
}
static Vec gradient (int c, int r) {
return gradients[c][r];
}
// your function, with y0 fixed. note my gridSize is not a double like yours.
public static double getPoint(int x, int y) {
Vec p = new Vec(x / (double)gridSize, y / (double)gridSize);
Vec d = new Vec(Math.floor(p.x), Math.floor(p.y));
int x0 = (int)d.x,
y0 = (int)d.y;
double d00 = gradient(x0 , y0 ).dot(p.sub(x0 , y0 )),
d01 = gradient(x0 , y0 + 1).dot(p.sub(x0 , y0 + 1)),
d10 = gradient(x0 + 1, y0 ).dot(p.sub(x0 + 1, y0 )),
d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1));
double fadeX = fade(p.x - d.x),
fadeY = fade(p.y - d.y);
double i1 = lerp(fadeX, d00, d10),
i2 = lerp(fadeX, d01, d11);
return lerp(fadeY, i1, i2);
}
public static void main (String[] args) {
// loop forever, regenerating gradients and resampling for range.
while (true) {
initializeGradient();
double minz = 0, maxz = 0;
for (int x = 0; x < gridSize * gridCells; ++ x) {
for (int y = 0; y < gridSize * gridCells; ++ y) {
double z = getPoint(x, y);
if (z < minz)
minz = z;
else if (z > maxz)
maxz = z;
}
}
System.out.println(minz + " " + maxz);
}
}
}
I am seeing values within the theoretical range of [-0.707, 0.707], although I am generally seeing values between -0.6 and 0.6; which could just be a consequence of the value distribution and a low sampling rate.