commit
939bf95a81
@ -0,0 +1,224 @@
|
||||
namespace Noise;
|
||||
|
||||
using System;
|
||||
|
||||
// Used for the hashing example that's commented out
|
||||
//using System.Text;
|
||||
//using System.Security.Cryptography;
|
||||
|
||||
// Based on https://adrianb.io/2014/08/09/perlinnoise.html
|
||||
|
||||
public class PerlinNoise {
|
||||
// Hash lookup table as defined by Ken Perlin. This is a randomly
|
||||
// arranged array of all numbers from 0-255 inclusive.
|
||||
// Works as the seed.
|
||||
private static readonly int[] permutation = { 151,160,137,91,90,15,
|
||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
|
||||
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
|
||||
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
|
||||
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
|
||||
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
|
||||
};
|
||||
|
||||
// Later on, in the hash function, access similar to p[p[_]+255+1] is performed
|
||||
// p contains two copies of permutation, len 512 in total.
|
||||
// The max number p[n] can return is 255, and 255+255+1 is 511,
|
||||
// which is the largest index in the 512 long array.
|
||||
private static readonly int[] p;
|
||||
|
||||
// Controls intentional noise repeating
|
||||
// Do note the noise will repeat each 256 anyway, due to the hashing function
|
||||
// (to be exact, the preprocessing of the input for the hashing function)
|
||||
private static readonly int repeat = 0;
|
||||
|
||||
static PerlinNoise() {
|
||||
p = new int[512];
|
||||
for(int i=0;i<512;i++) {
|
||||
p[i] = permutation[i%256];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO figure out how the vectors were chosen
|
||||
// TODO figure out whether there is bias due to a few vectors being repeated
|
||||
// TODO experiment with other vectors
|
||||
//
|
||||
// Choose a random vector from
|
||||
//
|
||||
// The 12 vectors
|
||||
// (1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0),
|
||||
// (1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1),
|
||||
// (0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1)
|
||||
//
|
||||
// Then calculate the dot product.
|
||||
// Take as an example the first one, (1,1,0)
|
||||
// The dot product would thus be 1x + 1y + 0z. Thus, x + y
|
||||
//
|
||||
// The equations below are just simplified dot products.
|
||||
//
|
||||
// We have 12 vectors but 16 cases, so some of the vectors repeat.
|
||||
//
|
||||
// The vectors represent the middles of edges of a cube.
|
||||
// A cube has 12 edges, so 12 vectors.
|
||||
public static double grad(int hash, double x, double y, double z) {
|
||||
switch(hash & 0xF)
|
||||
{
|
||||
// Unique
|
||||
case 0x0: return x + y; // 1, 1, 0 A
|
||||
case 0x1: return -x + y; // -1, 1, 0 B
|
||||
case 0x2: return -y + z; // 0, -1, 1 C
|
||||
case 0x3: return -x - y; // -1, -1, 0
|
||||
case 0x4: return x + z; // 1, 0, 1
|
||||
case 0x5: return -x + z; // -1, 0, 1
|
||||
case 0x6: return x - z; // 1, 0, -1
|
||||
case 0x7: return -x - z; // -1, 0, -1
|
||||
case 0x8: return y + z; // 0, 1, 1
|
||||
case 0x9: return x - y; // 1, -1, 0
|
||||
case 0xA: return y - z; // 0, 1, -1
|
||||
case 0xB: return -y - z; // 0, -1, -1
|
||||
// Repetitions
|
||||
case 0xC: return y + x; // 1, 1, 0 A
|
||||
case 0xD: return -y + z; // 0, -1, 1 C
|
||||
case 0xE: return y - x; // -1, 1, 0 B
|
||||
case 0xF: return -y - z; // 0, -1, 1 C
|
||||
default: return 0; // never happens
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Unsure of other properties it needs to satisfy
|
||||
// TODO Unsure why it's required, but without it,
|
||||
// edges between cells/cubes become visible
|
||||
// TODO There are indeed other properties.
|
||||
// 1 / (1 + Math.Exp(-12 * (x - 0.5)))
|
||||
// satisfies the 3 defined ones, but does not give good results.
|
||||
//
|
||||
// A magical easing function
|
||||
//
|
||||
// Intended range is 0..1 -> 0..1
|
||||
// It's a simple sigmoid that satisfies
|
||||
// f(0) = 0
|
||||
// f(1) = 1
|
||||
//
|
||||
// Fade function as defined by Ken Perlin. This eases coordinate values
|
||||
// so that they will ease towards integral values. This ends up smoothing
|
||||
// the final output.
|
||||
public static double fade(double t) {
|
||||
// 6t^5 - 15t^4 + 10t^3
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
// Incrementing function that wraps around based on `repeat`
|
||||
public static int inc(int num) {
|
||||
num++;
|
||||
if (repeat > 0) num %= repeat;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
// Linear Interpolate
|
||||
public static double lerp(double a, double b, double x) {
|
||||
return a + x * (b - a);
|
||||
}
|
||||
|
||||
// Hash function
|
||||
// Any hash function can be utilized (for example sha256 works), though this one is fast and simple.
|
||||
// This hash function performs 3 random "jumps" in the permutations array,
|
||||
// seeded & offset by the coordinates and offset by the number it landed on.
|
||||
private static int hash(int xi, int yi, int zi) {
|
||||
return p[p[p[xi]+yi]+zi];
|
||||
}
|
||||
|
||||
// Here is a Sha256 replacement, to show that the permutations are literally just a prng with nothing special
|
||||
//private static int hash(int xi, int yi, int zi) {
|
||||
// string inputString = $"{xi},{yi},{zi}";
|
||||
// byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(inputString));
|
||||
// return BitConverter.ToInt32(hashBytes, 0);
|
||||
//}
|
||||
|
||||
// TODO figure out why n.0 always returns 0.5, and n.5 always returns numbers like
|
||||
// 0.75, 0.5 or 0.375. Overall, the noise seems to break down when it is not "zoomed in" enough.
|
||||
public static double perlin(double x, double y, double z) {
|
||||
// TODO this is probably useless
|
||||
// If we have any repeat on, change the coordinates to their "local" repetitions
|
||||
if(repeat > 0) {
|
||||
x = x%repeat;
|
||||
y = y%repeat;
|
||||
z = z%repeat;
|
||||
}
|
||||
|
||||
// Calculate the "unit cube" that the point asked will be located in
|
||||
// The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
|
||||
// plus 1. Next we calculate the location (from 0.0 to 1.0) in that cube.
|
||||
|
||||
// This is just a singular corner of the cube.
|
||||
// Modulos with 256 - this causes integers to repeat every 256.
|
||||
// Thus, 0.25 = 256.25, 3.6 = 259.6...
|
||||
// And yes, this causes a visual repeating pattern.
|
||||
// The edge is not visible under normal conditions, though.
|
||||
int xi = (int)x & 255;
|
||||
int yi = (int)y & 255;
|
||||
int zi = (int)z & 255;
|
||||
|
||||
// 0..1 coordinates within the cube
|
||||
double xf = x-(int)x;
|
||||
double yf = y-(int)y;
|
||||
double zf = z-(int)z;
|
||||
|
||||
// TODO figure out why the lerping coordinates are eased rather than anything else.
|
||||
// Coordinates used for the lerping
|
||||
double u = fade(xf);
|
||||
double v = fade(yf);
|
||||
double w = fade(zf);
|
||||
|
||||
// Magic hash
|
||||
// Compute 8 hashes for each vertex of the cube, from each coordinate
|
||||
int aaa, aba, aab, abb, baa, bba, bab, bbb;
|
||||
aaa = hash( xi, yi, zi);
|
||||
aba = hash( xi, inc(yi), zi);
|
||||
aab = hash( xi, yi, inc(zi));
|
||||
abb = hash( xi, inc(yi), inc(zi));
|
||||
baa = hash(inc(xi), yi, zi);
|
||||
bba = hash(inc(xi), inc(yi), zi);
|
||||
bab = hash(inc(xi), yi, inc(zi));
|
||||
bbb = hash(inc(xi), inc(yi), inc(zi));
|
||||
|
||||
// TODO figure out how the -1s work
|
||||
// TODO the u, v, and w being used in this order matters. Why?
|
||||
// TODO figure out why lerping, and this particular way works
|
||||
//
|
||||
// This is then used as the seed for the grad() function that uses it to
|
||||
// pick a random edge vector and compute the dot product between it and the input vector,
|
||||
// offset into 8 corners, where one is the origin corner of the cube
|
||||
// (This works fine because the origin corner of the cube is also the origin of the vectors)
|
||||
// Results in vectors pointing from the corners to the input point specified by the input vector.
|
||||
//
|
||||
// The gradient function calculates the dot product between a pseudorandom
|
||||
// gradient vector and the vector from the input coordinate to the 8
|
||||
// surrounding points in its unit cube.
|
||||
// This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
|
||||
// values we made earlier.
|
||||
double x1, x2, x3, x4, 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);
|
||||
|
||||
x3 = lerp(grad(aab, xf , yf , zf-1),
|
||||
grad(bab, xf-1, yf , zf-1), u);
|
||||
x4 = lerp(grad(abb, xf , yf-1, zf-1),
|
||||
grad(bbb, xf-1, yf-1, zf-1), u);
|
||||
y2 = lerp(x3, x4, v);
|
||||
|
||||
// For convenience we bind the result to 0 - 1 (theoretical min/max before is [-1, 1])
|
||||
double res = (lerp (y1, y2, w)+1)/2;
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue