-
-
Save mgdevereux/ce70182d08a22f05f2d16f389aff4506 to your computer and use it in GitHub Desktop.
OpenSimplex Noise Refactored for C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* OpenSimplex Noise in C# | |
| * Ported from https://gist.github.com/KdotJPG/b1270127455a94ac5d19 | |
| * and heavily refactored to improve performance and readability of the code. | |
| * The main difference is the removal of the "extra" points which means it won't | |
| * have exactly the same output as the original code. In terms of visual quality, | |
| * I've noticed no difference, but in terms of performance it's nearly twice as | |
| * fast in 3rd and 4th dimensions. */ | |
| using System; | |
| namespace NoiseTest | |
| { | |
| public class OpenSimplexNoise | |
| { | |
| private const double STRETCH_2D = -0.211324865405187; //(1/Math.sqrt(2+1)-1)/2; | |
| private const double STRETCH_3D = -1.0 / 6.0; //(1/Math.sqrt(3+1)-1)/3; | |
| private const double STRETCH_4D = -0.138196601125011; //(1/Math.sqrt(4+1)-1)/4; | |
| private const double SQUISH_2D = 0.366025403784439; //(Math.sqrt(2+1)-1)/2; | |
| private const double SQUISH_3D = 1.0 / 3.0; //(Math.sqrt(3+1)-1)/3; | |
| private const double SQUISH_4D = 0.309016994374947; //(Math.sqrt(4+1)-1)/4; | |
| private const double NORM_2D = 47; | |
| private const double NORM_3D = 103; | |
| private const double NORM_4D = 30; | |
| private byte[] perm; | |
| private byte[] perm2D; | |
| private byte[] perm3D; | |
| private byte[] perm4D; | |
| //Gradients for 2D. They approximate the directions to the | |
| //vertices of an octagon from the center. | |
| private static double[] gradients2D = new double[] | |
| { | |
| 5, 2, 2, 5, | |
| -5, 2, -2, 5, | |
| 5, -2, 2, -5, | |
| -5, -2, -2, -5, | |
| }; | |
| //Gradients for 3D. They approximate the directions to the | |
| //vertices of a rhombicuboctahedron from the center, skewed so | |
| //that the triangular and square facets can be inscribed inside | |
| //circles of the same radius. | |
| private static double[] gradients3D = new double[] | |
| { | |
| -11, 4, 4, -4, 11, 4, -4, 4, 11, | |
| 11, 4, 4, 4, 11, 4, 4, 4, 11, | |
| -11, -4, 4, -4, -11, 4, -4, -4, 11, | |
| 11, -4, 4, 4, -11, 4, 4, -4, 11, | |
| -11, 4, -4, -4, 11, -4, -4, 4, -11, | |
| 11, 4, -4, 4, 11, -4, 4, 4, -11, | |
| -11, -4, -4, -4, -11, -4, -4, -4, -11, | |
| 11, -4, -4, 4, -11, -4, 4, -4, -11, | |
| }; | |
| //Gradients for 4D. They approximate the directions to the | |
| //vertices of a disprismatotesseractihexadecachoron from the center, | |
| //skewed so that the tetrahedral and cubic facets can be inscribed inside | |
| //spheres of the same radius. | |
| private static double[] gradients4D = new double[] | |
| { | |
| 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, | |
| -3, 1, 1, 1, -1, 3, 1, 1, -1, 1, 3, 1, -1, 1, 1, 3, | |
| 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, 1, 1, -1, 1, 3, | |
| -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, | |
| 3, 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, | |
| -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3, | |
| 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, | |
| -3, -1, -1, 1, -1, -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, | |
| 3, 1, 1, -1, 1, 3, 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, | |
| -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, -1, -1, 1, 1, -3, | |
| 3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, | |
| -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, | |
| 3, 1, -1, -1, 1, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, | |
| -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, | |
| 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, -1, 1, -1, -1, -3, | |
| -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, | |
| }; | |
| private static readonly Contribution2[][] contributions2D = | |
| { | |
| new Contribution2[] { new Contribution2(1, 1, 0), new Contribution2(1, 0, 1), new Contribution2(2, 1, 1) }, | |
| new Contribution2[] { new Contribution2(1, 1, 0), new Contribution2(1, 0, 1), new Contribution2(0, 0, 0) } | |
| }; | |
| private static readonly Contribution3[][] contributions3D = | |
| { | |
| new Contribution3[] { new Contribution3(0, 0, 0, 0), new Contribution3(1, 1, 0, 0), new Contribution3(1, 0, 1, 0), new Contribution3(1, 0, 0, 1) }, | |
| new Contribution3[] { new Contribution3(2, 1, 1, 0), new Contribution3(2, 1, 0, 1), new Contribution3(2, 0, 1, 1), new Contribution3(3, 1, 1, 1) }, | |
| new Contribution3[] { new Contribution3(1, 1, 0, 0), new Contribution3(1, 0, 1, 0), new Contribution3(1, 0, 0, 1), new Contribution3(2, 1, 1, 0), new Contribution3(2, 1, 0, 1), new Contribution3(2, 0, 1, 1)} | |
| }; | |
| private static readonly Contribution4[][] contributions4D = | |
| { | |
| new Contribution4[] { new Contribution4(0, 0, 0, 0, 0), new Contribution4(1, 1, 0, 0, 0), new Contribution4(1, 0, 1, 0, 0), new Contribution4(1, 0, 0, 1, 0), new Contribution4(1, 0, 0, 0, 1) }, | |
| new Contribution4[] { new Contribution4(3, 1, 1, 1, 0), new Contribution4(3, 1, 1, 0, 1), new Contribution4(3, 1, 0, 1, 1), new Contribution4(3, 0, 1, 1, 1), new Contribution4(4, 1, 1, 1, 1) }, | |
| new Contribution4[] { new Contribution4(1, 1, 0, 0, 0), new Contribution4(1, 0, 1, 0, 0), new Contribution4(1, 0, 0, 1, 0), new Contribution4(1, 0, 0, 0, 1), new Contribution4(2, 1, 1, 0, 0), new Contribution4(2, 1, 0, 1, 0), new Contribution4(2, 1, 0, 0, 1), new Contribution4(2, 0, 1, 1, 0), new Contribution4(2, 0, 1, 0, 1), new Contribution4(2, 0, 0, 1, 1) }, | |
| new Contribution4[] { new Contribution4(3, 1, 1, 1, 0), new Contribution4(3, 1, 1, 0, 1), new Contribution4(3, 1, 0, 1, 1), new Contribution4(3, 0, 1, 1, 1), new Contribution4(2, 1, 1, 0, 0), new Contribution4(2, 1, 0, 1, 0), new Contribution4(2, 1, 0, 0, 1), new Contribution4(2, 0, 1, 1, 0), new Contribution4(2, 0, 1, 0, 1), new Contribution4(2, 0, 0, 1, 1) } | |
| }; | |
| public OpenSimplexNoise() | |
| : this(DateTime.Now.Ticks) | |
| { | |
| } | |
| //Initializes the class using a permutation array generated from a 64-bit seed. | |
| //Generates a proper permutation (i.e. doesn't merely perform N successive pair swaps on a base array) | |
| //Uses a simple 64-bit LCG. | |
| public OpenSimplexNoise(long seed) | |
| { | |
| perm = new byte[256]; | |
| perm2D = new byte[256]; | |
| perm3D = new byte[256]; | |
| perm4D = new byte[256]; | |
| var source = new byte[256]; | |
| for (int i = 0; i < 256; i++) | |
| { | |
| source[i] = (byte)i; | |
| } | |
| seed = seed * 6364136223846793005L + 1442695040888963407L; | |
| seed = seed * 6364136223846793005L + 1442695040888963407L; | |
| seed = seed * 6364136223846793005L + 1442695040888963407L; | |
| for (int i = 255; i >= 0; i--) | |
| { | |
| seed = seed * 6364136223846793005L + 1442695040888963407L; | |
| int r = (int)((seed + 31) % (i + 1)); | |
| if (r < 0) | |
| { | |
| r += (i + 1); | |
| } | |
| perm[i] = source[r]; | |
| perm2D[i] = (byte)((perm[i] % 8) * 2); | |
| perm3D[i] = (byte)((perm[i] % 12) * 3); | |
| perm4D[i] = (byte)((perm[i] % 64) * 4); | |
| source[r] = source[i]; | |
| } | |
| } | |
| public double Evaluate(double x, double y) | |
| { | |
| //Place input coordinates onto grid. | |
| var stretchOffset = (x + y) * STRETCH_2D; | |
| var xs = x + stretchOffset; | |
| var ys = y + stretchOffset; | |
| //Floor to get grid coordinates of rhombus (stretched square) super-cell origin. | |
| var xsb = xs > 0 ? (int)xs : (int)xs - 1; | |
| var ysb = ys > 0 ? (int)ys : (int)ys - 1; | |
| //Skew out to get actual coordinates of rhombus origin. We'll need these later. | |
| var squishOffset = (xsb + ysb) * SQUISH_2D; | |
| var xb = xsb + squishOffset; | |
| var yb = ysb + squishOffset; | |
| //Positions relative to origin point. | |
| var dx0 = x - xb; | |
| var dy0 = y - yb; | |
| //Compute grid coordinates relative to rhombus origin. | |
| var xins = xs - xsb; | |
| var yins = ys - ysb; | |
| //Sum those together to get a value that determines which region we're in. | |
| var inSum = xins + yins; | |
| Contribution2[] contributions; | |
| if (inSum > 1) //We're inside the triangle (2-Simplex) at (0,0) | |
| { | |
| contributions = contributions2D[0]; | |
| } | |
| else //We're inside the triangle (2-Simplex) at (1,1) | |
| { | |
| contributions = contributions2D[1]; | |
| } | |
| double value = 0; | |
| foreach (var c in contributions) | |
| { | |
| var dx = dx0 + c.dx; | |
| var dy = dy0 + c.dy; | |
| var attn = 2 - dx * dx - dy * dy; | |
| if (attn > 0) | |
| { | |
| var px = xsb + c.xsb; | |
| var py = ysb + c.ysb; | |
| var i = perm2D[(perm[px & 0xFF] + py) & 0xFF]; | |
| var valuePart = gradients2D[i] * dx + gradients2D[i + 1] * dy; | |
| attn *= attn; | |
| value += attn * attn * valuePart; | |
| } | |
| } | |
| return value / NORM_2D; | |
| } | |
| public double Evaluate(double x, double y, double z) | |
| { | |
| //Place input coordinates on simplectic honeycomb. | |
| var stretchOffset = (x + y + z) * STRETCH_3D; | |
| var xs = x + stretchOffset; | |
| var ys = y + stretchOffset; | |
| var zs = z + stretchOffset; | |
| //Floor to get simplectic honeycomb coordinates of rhombohedron (stretched cube) super-cell origin. | |
| var xsb = xs > 0 ? (int)xs : (int)xs - 1; | |
| var ysb = ys > 0 ? (int)ys : (int)ys - 1; | |
| var zsb = zs > 0 ? (int)zs : (int)zs - 1; | |
| //Skew out to get actual coordinates of rhombohedron origin. We'll need these later. | |
| var squishOffset = (xsb + ysb + zsb) * SQUISH_3D; | |
| var xb = xsb + squishOffset; | |
| var yb = ysb + squishOffset; | |
| var zb = zsb + squishOffset; | |
| //Positions relative to origin point. | |
| var dx0 = x - xb; | |
| var dy0 = y - yb; | |
| var dz0 = z - zb; | |
| //Compute simplectic honeycomb coordinates relative to rhombohedral origin. | |
| var xins = xs - xsb; | |
| var yins = ys - ysb; | |
| var zins = zs - zsb; | |
| //Sum those together to get a value that determines which region we're in. | |
| var inSum = xins + yins + zins; | |
| Contribution3[] contributions; | |
| if (inSum <= 1) //We're inside the tetrahedron (3-Simplex) at (0,0,0) | |
| { | |
| contributions = contributions3D[0]; | |
| } | |
| else if (inSum >= 2) //We're inside the tetrahedron (3-Simplex) at (1,1,1) | |
| { | |
| contributions = contributions3D[1]; | |
| } | |
| else //We're inside the octahedron (Rectified 3-Simplex) in between. | |
| { | |
| contributions = contributions3D[2]; | |
| } | |
| double value = 0; | |
| foreach (var c in contributions) | |
| { | |
| var dx = dx0 + c.dx; | |
| var dy = dy0 + c.dy; | |
| var dz = dz0 + c.dz; | |
| var attn = 2 - dx * dx - dy * dy - dz * dz; | |
| if (attn > 0) | |
| { | |
| var px = xsb + c.xsb; | |
| var py = ysb + c.ysb; | |
| var pz = zsb + c.zsb; | |
| var i = perm3D[(perm[(perm[px & 0xFF] + py) & 0xFF] + pz) & 0xFF]; | |
| var valuePart = gradients3D[i] * dx + gradients3D[i + 1] * dy + gradients3D[i + 2] * dz; | |
| attn *= attn; | |
| value += attn * attn * valuePart; | |
| } | |
| } | |
| return value / NORM_3D; | |
| } | |
| public double Evaluate(double x, double y, double z, double w) | |
| { | |
| //Place input coordinates on simplectic honeycomb. | |
| var stretchOffset = (x + y + z + w) * STRETCH_4D; | |
| var xs = x + stretchOffset; | |
| var ys = y + stretchOffset; | |
| var zs = z + stretchOffset; | |
| var ws = w + stretchOffset; | |
| //Floor to get simplectic honeycomb coordinates of rhombo-hypercube super-cell origin. | |
| var xsb = xs > 0 ? (int)xs : (int)xs - 1; | |
| var ysb = ys > 0 ? (int)ys : (int)ys - 1; | |
| var zsb = zs > 0 ? (int)zs : (int)zs - 1; | |
| var wsb = ws > 0 ? (int)ws : (int)ws - 1; | |
| //Skew out to get actual coordinates of stretched rhombo-hypercube origin. We'll need these later. | |
| var squishOffset = (xsb + ysb + zsb + wsb) * SQUISH_4D; | |
| var xb = xsb + squishOffset; | |
| var yb = ysb + squishOffset; | |
| var zb = zsb + squishOffset; | |
| var wb = wsb + squishOffset; | |
| //Positions relative to origin point. | |
| var dx0 = x - xb; | |
| var dy0 = y - yb; | |
| var dz0 = z - zb; | |
| var dw0 = w - wb; | |
| //Compute simplectic honeycomb coordinates relative to rhombo-hypercube origin. | |
| var xins = xs - xsb; | |
| var yins = ys - ysb; | |
| var zins = zs - zsb; | |
| var wins = ws - wsb; | |
| //Sum those together to get a value that determines which region we're in. | |
| var inSum = xins + yins + zins + wins; | |
| Contribution4[] contributions; | |
| if (inSum <= 1) // We're inside the pentachoron (4-Simplex) at (0,0,0,0) | |
| { | |
| contributions = contributions4D[0]; | |
| } | |
| else if (inSum >= 3) //We're inside the pentachoron (4-Simplex) at (1,1,1,1) | |
| { | |
| contributions = contributions4D[1]; | |
| } | |
| else if (inSum <= 2) // We're inside the first dispentachoron (Rectified 4-Simplex) | |
| { | |
| contributions = contributions4D[2]; | |
| } | |
| else // We're inside the second dispentachoron (Rectified 4-Simplex) | |
| { | |
| contributions = contributions4D[3]; | |
| } | |
| double value = 0; | |
| foreach (var c in contributions) | |
| { | |
| var dx = dx0 + c.dx; | |
| var dy = dy0 + c.dy; | |
| var dz = dz0 + c.dz; | |
| var dw = dw0 + c.dw; | |
| var attn = 2 - dx * dx - dy * dy - dz * dz - dw * dw; | |
| if (attn > 0) | |
| { | |
| var px = xsb + c.xsb; | |
| var py = ysb + c.ysb; | |
| var pz = zsb + c.zsb; | |
| var pw = wsb + c.wsb; | |
| var i = perm4D[(perm[(perm[(perm[px & 0xFF] + py) & 0xFF] + pz) & 0xFF] + pw) & 0xFF]; | |
| var valuePart = gradients4D[i] * dx + gradients4D[i + 1] * dy + gradients4D[i + 2] * dz + gradients4D[i + 3] * dw; | |
| attn *= attn; | |
| value += attn * attn * valuePart; | |
| } | |
| } | |
| return value / NORM_4D; | |
| } | |
| private class Contribution2 | |
| { | |
| public readonly double dx, dy; | |
| public readonly int xsb, ysb; | |
| public Contribution2(double multiplier, int xsb, int ysb) | |
| { | |
| dx = -xsb - multiplier * SQUISH_2D; | |
| dy = -ysb - multiplier * SQUISH_2D; | |
| this.xsb = xsb; | |
| this.ysb = ysb; | |
| } | |
| } | |
| private class Contribution3 | |
| { | |
| public readonly double dx, dy, dz; | |
| public readonly int xsb, ysb, zsb; | |
| public Contribution3(double multiplier, int xsb, int ysb, int zsb) | |
| { | |
| dx = -xsb - multiplier * SQUISH_3D; | |
| dy = -ysb - multiplier * SQUISH_3D; | |
| dz = -zsb - multiplier * SQUISH_3D; | |
| this.xsb = xsb; | |
| this.ysb = ysb; | |
| this.zsb = zsb; | |
| } | |
| } | |
| private class Contribution4 | |
| { | |
| public readonly double dx, dy, dz, dw; | |
| public readonly int xsb, ysb, zsb, wsb; | |
| public Contribution4(double multiplier, int xsb, int ysb, int zsb, int wsb) | |
| { | |
| dx = -xsb - multiplier * SQUISH_4D; | |
| dy = -ysb - multiplier * SQUISH_4D; | |
| dz = -zsb - multiplier * SQUISH_4D; | |
| dw = -wsb - multiplier * SQUISH_4D; | |
| this.xsb = xsb; | |
| this.ysb = ysb; | |
| this.zsb = zsb; | |
| this.wsb = wsb; | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment