You were correct in your original supposition that the function expects you to pass in a coordinate pair. Where you see x-1
in the code, it's just an intermediate value for the noise calculation. That value isn't used as an index into your array.
I have implemented the pseudocode in the following C++ program.
// Two-dimensional value noise based on Hugo Elias's description:
// http://freespace.virgin.net/hugo.elias/models/m_perlin.htm
#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;
int numX = 512,
numY = 512,
numOctaves = 7;
double persistence = 0.5;
#define maxPrimeIndex 10
int primeIndex = 0;
int primes[maxPrimeIndex][3] = {
{ 995615039, 600173719, 701464987 },
{ 831731269, 162318869, 136250887 },
{ 174329291, 946737083, 245679977 },
{ 362489573, 795918041, 350777237 },
{ 457025711, 880830799, 909678923 },
{ 787070341, 177340217, 593320781 },
{ 405493717, 291031019, 391950901 },
{ 458904767, 676625681, 424452397 },
{ 531736441, 939683957, 810651871 },
{ 997169939, 842027887, 423882827 }
};
double Noise(int i, int x, int y) {
int n = x + y * 57;
n = (n << 13) ^ n;
int a = primes[i][0], b = primes[i][1], c = primes[i][2];
int t = (n * (n * n * a + b) + c) & 0x7fffffff;
return 1.0 - (double)(t)/1073741824.0;
}
double SmoothedNoise(int i, int x, int y) {
double corners = (Noise(i, x-1, y-1) + Noise(i, x+1, y-1) +
Noise(i, x-1, y+1) + Noise(i, x+1, y+1)) / 16,
sides = (Noise(i, x-1, y) + Noise(i, x+1, y) + Noise(i, x, y-1) +
Noise(i, x, y+1)) / 8,
center = Noise(i, x, y) / 4;
return corners + sides + center;
}
double Interpolate(double a, double b, double x) { // cosine interpolation
double ft = x * 3.1415927,
f = (1 - cos(ft)) * 0.5;
return a*(1-f) + b*f;
}
double InterpolatedNoise(int i, double x, double y) {
int integer_X = x;
double fractional_X = x - integer_X;
int integer_Y = y;
double fractional_Y = y - integer_Y;
double v1 = SmoothedNoise(i, integer_X, integer_Y),
v2 = SmoothedNoise(i, integer_X + 1, integer_Y),
v3 = SmoothedNoise(i, integer_X, integer_Y + 1),
v4 = SmoothedNoise(i, integer_X + 1, integer_Y + 1),
i1 = Interpolate(v1, v2, fractional_X),
i2 = Interpolate(v3, v4, fractional_X);
return Interpolate(i1, i2, fractional_Y);
}
double ValueNoise_2D(double x, double y) {
double total = 0,
frequency = pow(2, numOctaves),
amplitude = 1;
for (int i = 0; i < numOctaves; ++i) {
frequency /= 2;
amplitude *= persistence;
total += InterpolatedNoise((primeIndex + i) % maxPrimeIndex,
x / frequency, y / frequency) * amplitude;
}
return total / frequency;
}
int main(int argc, char** args) {
if (argc >= 3) {
numX = atoi(args[1]);
numY = atoi(args[2]);
}
if (argc >= 4) {
numOctaves = atoi(args[3]);
}
if (argc >= 5) {
persistence = atof(args[4]);
}
if (argc >= 6) {
primeIndex = atoi(args[5]) % maxPrimeIndex;
}
fprintf(stderr, "numX: %d, numY: %d, numOctaves: %d, persistence: %.5f, ",
numX, numY, numOctaves, persistence);
fprintf(stderr, "primeIndex: %d\n", primeIndex);
printf("var rawNoise = [\n");
for (int y = 0; y < numY; ++y) {
for (int x = 0; x < numX; ++x) {
double noise = ValueNoise_2D(x, y);
if (x == 0) {
printf(" [");
}
printf("%.5f", noise);
if (x == numX-1) {
printf("]");
if (y == numY-1) {
printf("\n];\n");
} else {
printf(",\n");
}
} else {
printf(", ");
}
}
}
return 0;
}
This program accepts up to five arguments on the command line. The first four arguments correspond to the parameters numX
, numY
, numOctaves
, and persistence
, respectively.
The fifth argument is primeIndex
, an integer from 0 to 9, which determines which of the ten random-number generators is called first. Thus, you can get ten different results after fixing the values of the other four parameters.
The output of the program is a JavaScript array. If you store this output in a file called rawNoise.js
, you can load the following web page to view an image of the noise.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title> Demonstration of two-dimensional value noise </title>
<script src="rawNoise.js"></script>
<script>
var ValueNoise = {
noise: { raw: rawNoise }
};
ValueNoise.load = function () {
var g = ValueNoise,
raw = g.noise.raw,
numR = g.numR = raw.length,
numC = g.numC = raw[0].length,
minValue = raw[0][0],
maxValue = minValue;
for (var r = 0; r < numR; ++r) {
for (var c = 0; c < numC; ++c) {
maxValue = Math.max(maxValue, raw[r][c]);
minValue = Math.min(minValue, raw[r][c]);
}
}
var valueSpread = maxValue - minValue;
console.log(minValue, maxValue, valueSpread);
var container = document.getElementById('display'),
canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
imageData = context.createImageData(numC, numR),
data = imageData.data;
for (var r = 0; r < numR; ++r) {
for (var c = 0; c < numC; ++c) {
var value = raw[r][c],
scaled = Math.round(255 * (value - minValue) / valueSpread),
pos = r*4*numC + 4*c;
data[pos] = data[pos+1] = data[pos+2] = scaled;
data[pos+3] = 255;
}
}
console.log(imageData);
canvas.width = numC;
canvas.height = numR;
container.appendChild(canvas);
context.putImageData(imageData, 0, 0);
};
window.onload = ValueNoise.load;
</script>
</head>
<body>
<div id="wrapper">
<div id="display"></div>
</div><!--end wrapper -->
</body>
</html>
On a Unix-style command line, you can compile and run the C++ program like this:
g++ -O2 noise.cpp -o noise
./noise 800 800 9 0.65 3 > rawNoise.js
Then if you open the above web page, you'll see this image:
