💾 Archived View for capsule.adrianhesketh.com › 2015 › 03 › 29 › perlin-noise captured on 2023-06-16 at 16:37:35. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
I've been working on testing some realtime data visualisation architecture (SignalR / In-Memory Caching vs Ajax / Redis Queue) and I needed some test data.
I needed a smooth-ish curve (i.e. not totally random numbers), but something that didn't contain too much repetition (e.g. a sine wave) and Perlin Noise seemed like a good fit for this.
Until a few weeks ago I'd never heard of Perlin Noise, but I've just read the first couple of chapters of Nature of Code [0] which has a chapter on using pseudo-random numbers to simulate physical forces and generate textures.
The book uses Processing [1] which has a built-in function for generating Perlin Noise. You can try it out yourself on Khan Academy's [2] Programming Natural Simulations [3] course. It's also worth having a read about it on Ken Perlin's Website: [4]
C# implementations I found were 2d or 3d so I minimally ported Ken Perlin's 1983 C code to C# (LinqPad script below). Maybe you'll find it useful...
1d noise:
2d noise:
void Main() { // Uses Oxyplot.WindowsForms. var noise = new PerlinNoise(); var results = new List<double>(); for(double i = 0.0d; i < 100; i += 0.01d) { results.Add(noise.Noise(i)); } var scatterSeries = new ScatterSeries(); scatterSeries.Points.AddRange(results.Select((r, i) => new ScatterPoint(i, r, 1d))); var chart = new PlotModel(); chart.Series.Add(scatterSeries); var view = new PlotView() { Model = chart, }; view.Dump(); CreateRandomTexture(1366, 300).Dump(); } public static class OxyPlotExtensions { public static void AddScatterSeries(this PlotModel model, IEnumerable<double> xSeries, IEnumerable<double> ySeries) { model.AddScatterSeries(xSeries, ySeries, OxyColors.Automatic); } public static void AddScatterSeries(this PlotModel model, IEnumerable<double> xSeries, IEnumerable<double> ySeries, OxyColor color) { var scatterSeries = new ScatterSeries() { MarkerFill = color, MarkerSize = 1, }; foreach (var item in xSeries.Zip(ySeries, (x, y) => new { x, y })) { scatterSeries.Points.Add(new ScatterPoint(item.x, item.y)); } model.Series.Add(scatterSeries); } } public Bitmap CreateRandomTexture(int width, int height) { var noise = new PerlinNoise(); var bmp = new Bitmap(width, height); var xOffset = 30d; for(int x = 0; x < width; x ++) { var yOffset = 500d; for(int y = 0; y < height; y ++) { var noiseValue = noise.Noise(xOffset, yOffset); var rgb = (int)Math.Abs(Map(noiseValue, -1d, 1d, 100, 255)); bmp.SetPixel(x, y, Color.FromArgb(rgb, rgb, rgb)); yOffset += 0.01d; } xOffset += 0.01d; } return bmp; } public double Map(double value, double sourceRangeMinimum, double sourceRangeMaximum, double targetRangeMinimum, double targetRangeMaximum) { if ((sourceRangeMaximum - sourceRangeMinimum) == 0) { return (targetRangeMinimum + targetRangeMaximum) / 2; } return targetRangeMinimum + (value - sourceRangeMinimum) * (targetRangeMaximum - targetRangeMinimum) / (sourceRangeMaximum - sourceRangeMinimum); } public class PerlinNoise { /* coherent noise function over 1, 2 or 3 dimensions */ /* (copyright Ken Perlin) */ const int B = 0x100; // 256 const int BM = 0xff; const int N = 0x1000; const int NP = 12; /* 2^N */ const int NM = 0xfff; int[] p = new int[B + B + 2]; double[][] g3 = new double[B + B + 2][]; // each child needs 3 double[][] g2 = new double[B + B + 2][]; // each child needs 2 double[] g1 = new double[B + B + 2]; private Random rnd = new Random(); public PerlinNoise() { int i, j, k; SizeArray(g2, 2); SizeArray(g3, 3); for (i = 0 ; i < B ; i++) { p[i] = i; g1[i] = (double)((rnd.Next() % (B + B)) - B) / B; // Size the array. for (j = 0 ; j < 2 ; j++) g2[i][j] = (float)((rnd.Next() % (B + B)) - B) / B; Normalize(g2[i]); g3[i] = new double[3]; for (j = 0 ; j < 3 ; j++) g3[i][j] = (float)((rnd.Next() % (B + B)) - B) / B; Normalize(g3[i]); } while (--i > 0) { k = p[i]; p[i] = p[j = rnd.Next() % B]; p[j] = k; } 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]; for (j = 0 ; j < 3 ; j++) g3[B + i][j] = g3[i][j]; } } private static double s_curve(double t) { return t * t * (3.0d - 2.0d * t); } private static double lerp(double t, double a, double b) { return a + t * (b - a); } public double Noise(double x) { double t = x + N; int bx0 = ((int)t) & BM; int bx1 = (bx0+1) & BM; double rx0 = t - (int)t; double rx1 = rx0 - 1.0d; double sx = s_curve(rx0); double u = rx0 * g1[ p[ bx0 ] ]; double v = rx1 * g1[ p[ bx1 ] ]; return lerp(sx, u, v); } private static double at2(double[] q, double rx, double ry) { return rx * q[0] + ry * q[1]; } public double Noise(double x, double y) { // setup(0, bx0,bx1, rx0,rx1); double t = x + N; int bx0 = ((int)t) & BM; int bx1 = (bx0+1) & BM; double rx0 = t - (int)t; double rx1 = rx0 - 1.0d; //setup(1, by0,by1, ry0,ry1); t = y + N; int by0 = ((int)t) & BM; int by1 = (by0+1) & BM; double ry0 = t - (int)t; double ry1 = ry0 - 1.0d; int i = p[ bx0 ]; int j = p[ bx1 ]; int b00 = p[ i + by0 ]; int b10 = p[ j + by0 ]; int b01 = p[ i + by1 ]; int b11 = p[ j + by1 ]; double sx = s_curve(rx0); double sy = s_curve(ry0); double[] q; q = g2[b00]; double u = at2(q, rx0,ry0); q = g2[ b10 ] ; double v = at2(q, rx1,ry0); double a = lerp(sx, u, v); q = g2[ b01 ] ; u = at2(q, rx0,ry1); q = g2[ b11 ] ; v = at2(q, rx1,ry1); double b = lerp(sx, u, v); return lerp(sy, a, b); } private static double at3(double[] q, double rx, double ry, double rz) { return rx * q[0] + ry * q[1] + rz * q[2]; } public double Noise(double x, double y, double z) { int bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11; double rx0, rx1, ry0, ry1, rz0, rz1, sy, sz, a, b, c, d, t, u, v; int i, j; // setup(0, bx0,bx1, rx0,rx1); t = x + N; bx0 = ((int)t) & BM; bx1 = (bx0+1) & BM; rx0 = t - (int)t; rx1 = rx0 - 1.0d; //setup(1, by0,by1, ry0,ry1); t = y + N; by0 = ((int)t) & BM; by1 = (by0+1) & BM; ry0 = t - (int)t; ry1 = ry0 - 1.0d; // setup(2, bz0,bz1, rz0,rz1); t = z + N; bz0 = ((int)t) & BM; bz1 = (bz0+1) & BM; rz0 = t - (int)t; rz1 = rz0 - 1.0d; i = p[ bx0 ]; j = p[ bx1 ]; b00 = p[ i + by0 ]; b10 = p[ j + by0 ]; b01 = p[ i + by1 ]; b11 = p[ j + by1 ]; t = s_curve(rx0); sy = s_curve(ry0); sz = s_curve(rz0); double[] q; q = g3[ b00 + bz0 ] ; u = at3(q, rx0,ry0,rz0); q = g3[ b10 + bz0 ] ; v = at3(q, rx1,ry0,rz0); a = lerp(t, u, v); q = g3[ b01 + bz0 ] ; u = at3(q, rx0,ry1,rz0); q = g3[ b11 + bz0 ] ; v = at3(q, rx1,ry1,rz0); b = lerp(t, u, v); c = lerp(sy, a, b); q = g3[ b00 + bz1 ] ; u = at3(q, rx0,ry0,rz1); q = g3[ b10 + bz1 ] ; v = at3(q, rx1,ry0,rz1); a = lerp(t, u, v); q = g3[ b01 + bz1 ] ; u = at3(q, rx0,ry1,rz1); q = g3[ b11 + bz1 ] ; v = at3(q, rx1,ry1,rz1); b = lerp(t, u, v); d = lerp(sy, a, b); return lerp(sz, c, d); } private static void Normalize(double[] vec) { var sumOfSquares = vec.Select(v => v * v).Sum(); var squareRootOfSumOfSquares = Math.Sqrt(sumOfSquares); for(int i = 0; i < vec.Length; i ++) { vec[i] = vec[i] / squareRootOfSumOfSquares; } } private static void SizeArray(double[][] array, int childDimensions) { for(int i = 0; i < array.Length; i++) { array[i] = new double[childDimensions]; } } }
Realtime Sparklines with SignalR
WCF - Client Proxy Creation Performance with Ninject