I wanted to challenge myself and create my own noise generator which would work with arbitrary many dimensions, have no symmetry artifacts around the origin, etc. I also wanted it to be linear, because I wanted to know how it would look like.
The code below is the finished product, but if you run it, you can notice some significant diagonal artifacts, as if the noise was correlated across the dimensions. Does anyone know why this might be the case? I took care to randomize everything as much as possible, even the dimensions themselves, so this is a bit surprising to me.
Also, the code is quite slow, so improvements in this regard are also appreciated.
import java.util.Random;
import java.time.Instant;
class _LinearNoise {
// the main function with which the noise is called
public double getNoise(double... pos) {
double sum = 0, amp = 1, scale = 1;
for (int i = 0; i < LOD; i++) {
sum += oneIter(scale, pos) * amp;
scale /= scaleGain;
amp *= ampGain;
}
// normalization... ensures the largest possible value is 1
return sum * (1 - ampGain) / (1 - Math.pow(ampGain, LOD));
}
// various convenient constructors
public _LinearNoise() {
setInternalVariables(getRandomSeed(), 8, 0.5, 0.5);
}
public _LinearNoise(long seed) {
setInternalVariables(seed, 8, 0.5, 0.5);
}
public _LinearNoise(long LOD, double scaleGain, double ampGain) {
setInternalVariables(getRandomSeed(), LOD, scaleGain, ampGain);
}
public _LinearNoise(long seed, long LOD, double scaleGain, double ampGain) {
setInternalVariables(seed, LOD, scaleGain, ampGain);
}
// getters and setters
public void setSeed(long seed) { this.seed = seed; }
public long getSeed() { return seed; }
public void setLOD(long LOD) { this.LOD = LOD; }
public long getLOD() { return LOD; }
public void setScaleGain(long scaleGain) { this.scaleGain = scaleGain; }
public double getScaleGain() { return scaleGain; }
public void setAmpGain(long ampGain) { this.ampGain = ampGain; }
public double getAmpGain() { return ampGain; }
private long seed, LOD;
private double scaleGain, ampGain;
// called by the constructors to initialize the variables
private void setInternalVariables(long seed, long LOD, double scaleGain, double ampGain) {
this.seed = seed;
this.LOD = LOD;
this.scaleGain = scaleGain;
this.ampGain = ampGain;
}
// generates an "actual" random seed; called if no seed is provided
private long getRandomSeed() {
return new Random(Instant.now().toEpochMilli()).nextLong();
}
// the actual algorithm
private double oneIter(double scale, double... pos) {
int dim = pos.length;
double[] scaledPos = new double[dim]; // for LOD calculation
long[] wholePart = new long[dim];
double[] fracPart = new double[dim];
long[] dimOffset = new long[dim]; // gives each coordinate term a specific offset
Random dimOffsetGenerator = new Random(seed);
for (int i = 0; i < dim; i++) {
scaledPos[i] = pos[i] * scale;
wholePart[i] = (long)Math.floor(scaledPos[i]);
fracPart[i] = scaledPos[i] - wholePart[i];
dimOffset[i] = dimOffsetGenerator.nextLong();
}
// each point in the noise space is surrounded by 2^dim grid points,
// whose values are given randomly (all other points are interpolated from these)
int vertCount = 2 << dim;
double[] vertVals = new double[vertCount];
// calculating the random value associated with each vertex:
for (int i = 0; i < vertCount; i++) {
long reduction = 0;
// processing each coordinate term for this specific vertex:
for (int j = 0; j < dim; j++) {
// pos_j = the j-th coordinate of the i-th vertex
long pos_j = wholePart[j] + ((i >> j) & 1);
// pos_j and dimOffset together seed another generator
// so basically this is like the "hash" of pos_j + dimOffset
// ...they are then added up, because I can't come up with a better reduction scheme
reduction += new Random(pos_j + dimOffset[j]).nextLong();
}
// the "hash" plus the seed value are then passed to another generator:
vertVals[i] = new Random(reduction + seed).nextDouble();
}
// the linear interpolation algorithm:
for (int i = 0; i < dim; i++) {
for (int j = 0; j < (vertCount >> (i + 1)); j++) {
int k = j*2;
double t = fracPart[i];
vertVals[j] = (1 - t) * vertVals[k] + t * vertVals[k+1];
}
}
// the interpolation reduces the range [0, x) to [0, x/2),
// so in the end, the result lies at index 0
return vertVals[0];
}
}
_LinearNoise L = new _LinearNoise(1, 0.5, 0.5);
void setup() {
size(800, 800);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
double val = L.getNoise(i * 0.1, j * 0.1);
stroke((int)(val * 255));
point(i, j);
}
}
}
void draw() {
}