Skip to content

Instantly share code, notes, and snippets.

@yaiqsa
Forked from Flafla2/Perlin_Tiled.cs
Last active July 10, 2017 16:56
Show Gist options
  • Select an option

  • Save yaiqsa/cfd7dc56014e2574af8713fa10688015 to your computer and use it in GitHub Desktop.

Select an option

Save yaiqsa/cfd7dc56014e2574af8713fa10688015 to your computer and use it in GitHub Desktop.
A slightly modified implementation of Ken Perlin's improved noise that allows for tiling the noise arbitrarily.
public class Perlin
{
/// <summary>
/// To my knowledge not used variable in my application.
/// </summary>
public int repeat;
/// <summary>
/// Instance of <see cref="Random"/> class.
/// </summary>
private static Random rand = new Random();
/// <summary>
/// First overload of the constructor.
/// To my knowledge not used in my application.
/// </summary>
/// <param name="repeat">To my knowledge not used variable in my application.</param>
public Perlin(int repeat = -1)
{
this.repeat = repeat;
}
/// <summary>
/// To my knowledge essential variable.
/// </summary>
private static readonly int[] p = new int[512];
/// <summary>
/// This method randomizes the Perlin Noise.
/// </summary>
/// <remarks>
/// This randomization is achieved by creating a random version of <see cref="permutation"/>.
/// </remarks>
private static void randomize()
{
int[] table = new int[256]; //Create an empty array
List<int> random = Enumerable.Range(0, 256).ToList(); //Create a list filled with numbers from 0 to 255
for (int i = 255; i >= 0; i--)
{
int randomNumber = rand.Next(0, i + 1);
table[i] = random[randomNumber]; //Put a number from a random index of the list into the array
random.RemoveAt(randomNumber); //Remove that number from the list
}
permutation = table;
for (int x = 0; x < 512; x++)
{
p[x] = permutation[x % 256];
}
}
/// <summary>
/// Method to access <see cref="randomize"/> from outside.
/// </summary>
public void Randomize()
{
randomize();
}
/// <summary>
/// This method returns the actual Perlin Noise value.
/// </summary>
/// <param name="x">x position in the Perlin Noise field. Best result when incremented with 0.0001 to 0.1.</param>
/// <param name="y">y position in the Perlin Noise field. Best result when incremented with 0.0001 to 0.1.</param>
/// <param name="z">z position in the Perlin Noise field. Best result when incremented with 0.0001 to 0.1.</param>
/// <param name="octaves">The amount of refining steps the Perlin Noise value is put trough.</param>
/// <param name="persistence">The amount the amplitude is multiplicated by in each octave.</param>
/// <returns>a <see cref="double"/> Perlin Noise value, between 0 and 1.</returns>
public double OctavePerlin(double x, double y, double z, int octaves, double persistence)
{
double total = 0;
double frequency = 1;
double amplitude = 1;
double maxValue = 0; // Used for normalizing result to 0.0 - 1.0
for (int i = 0; i < octaves; i++)
{
total += perlin(x * frequency, y * frequency, z * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence;
frequency *= 2;
}
return total / maxValue;
}
/// <summary>
/// Hash lookup table as defined by Ken Perlin.
/// </summary>
/// <remarks>
/// <para>
/// This is a randomly arranged array of all numbers from 0-255 inclusive.
/// When the <see cref="randomize"/> method is called, this <see cref="Array"/> is overwritten.
/// </para>
/// <para>
/// This used to be read-only in the original GitHub project.
/// </para>
/// <para>
/// Original array:
/// <code>
/// { 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
/// }
/// </code>
/// </para>
/// </remarks>
private static 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
};
/// <summary>
/// Second overload of the constructor.
/// </summary>
/// <remarks>
/// As far as I understand this overload of the constructor is the only one used in my application.
/// </remarks>
static Perlin()
{
randomize(); //This line can be removed to keep the original hash table
//p = new int[512]; //These lines should be uncommented to keep the original hash table.
//for (int x = 0; x < 512; x++)
//{
// p[x] = permutation[x % 256];
//}
}
/// <summary>
/// The method that creates a single octave Perlin Noise value.
/// </summary>
/// <remarks>
/// This method used to be public in the original GitHub project, but because I see no use in directly accessing this method (in contrary to <see cref="OctavePerlin(double, double, double, int, double)"/>, which calculates the octaves for you).
/// </remarks>
/// <param name="x">x position in the Perlin Noise field. Best result when incremented with 0.0001 to 0.1.</param>
/// <param name="y">y position in the Perlin Noise field. Best result when incremented with 0.0001 to 0.1.</param>
/// <param name="z">z position in the Perlin Noise field. Best result when incremented with 0.0001 to 0.1.</param>
/// <returns>a single octave <see cref="double"/> Perlin Noise value, between 0 and 1.</returns>
private double perlin(double x, double y, double z)
{
if (repeat > 0)
{ // If we have any repeat on, change the coordinates to their "local" repetitions
x = x % repeat;
y = y % repeat;
z = z % repeat;
}
int xi = (int)x & 255; // Calculate the "unit cube" that the point asked will be located in
int yi = (int)y & 255; // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
int zi = (int)z & 255; // plus 1. Next we calculate the location (from 0.0 to 1.0) in that cube.
double xf = x - (int)x; // We also fade the location to smooth the result.
double yf = y - (int)y;
double zf = z - (int)z;
double u = fade(xf);
double v = fade(yf);
double w = fade(zf);
int aaa, aba, aab, abb, baa, bba, bab, bbb;
aaa = p[p[p[xi] + yi] + zi];
aba = p[p[p[xi] + inc(yi)] + zi];
aab = p[p[p[xi] + yi] + inc(zi)];
abb = p[p[p[xi] + inc(yi)] + inc(zi)];
baa = p[p[p[inc(xi)] + yi] + zi];
bba = p[p[p[inc(xi)] + inc(yi)] + zi];
bab = p[p[p[inc(xi)] + yi] + inc(zi)];
bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)];
double x1, x2, y1, y2;
x1 = lerp(grad(aaa, xf, yf, zf), // The gradient function calculates the dot product between a pseudorandom
grad(baa, xf - 1, yf, zf), // gradient vector and the vector from the input coordinate to the 8
u); // surrounding points in its unit cube.
x2 = lerp(grad(aba, xf, yf - 1, zf), // This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
grad(bba, xf - 1, yf - 1, zf), // values we made earlier.
u);
y1 = lerp(x1, x2, v);
x1 = lerp(grad(aab, xf, yf, zf - 1),
grad(bab, xf - 1, yf, zf - 1),
u);
x2 = lerp(grad(abb, xf, yf - 1, zf - 1),
grad(bbb, xf - 1, yf - 1, zf - 1),
u);
y2 = lerp(x1, x2, v);
return (lerp(y1, y2, w) + 1) / 2; // For convenience we bound it to 0 - 1 (theoretical min/max before is -1 - 1)
}
/// <summary>
/// To my knowledge not in my application used method.
/// </summary>
/// <param name="num">To my knowledge not in my application used variable.</param>
/// <returns>Product of calculation.</returns>
public int inc(int num)
{
num++;
if (repeat > 0) num %= repeat;
return num;
}
/// <summary>
/// To my knowledge essential method to calculate Perlin Noise values.
/// </summary>
/// <remarks>
/// This method used to be public in the original GitHub project, but because I see no use in directly accessing this method.
/// </remarks>
/// <param name="hash"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <returns></returns>
private static double grad(int hash, double x, double y, double z)
{
int h = hash & 15; // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
double u = h < 8 /* 0b1000 */ ? x : y; // If the most significant bit (MSB) of the hash is 0 then set u = x. Otherwise y.
double v; // In Ken Perlin's original implementation this was another conditional operator (?:). I
// expanded it for readability.
if (h < 4 /* 0b0100 */) // If the first and second significant bits are 0 set v = y
v = y;
else if (h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/)// If the first and second significant bits are 1 set v = x
v = x;
else // If the first and second significant bits are not equal (0/1, 1/0) set v = z
v = z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); // Use the last 2 bits to decide if u and v are positive or negative. Then return their addition.
}
/// <summary>
/// Fade function as defined by Ken Perlin.
/// </summary>
/// <remarks>
/// <para>
/// This method used to be public in the original GitHub project, but because I see no use in directly accessing this method.
/// </para>
/// <para>
/// This function eases coordinate values so that they will "ease" towards integral values.
/// This ends up smoothing the final output.
/// </para>
/// </remarks>
/// <param name="t">to-be-faded variable.</param>
/// <returns>6t^5 - 15t^4 + 10t^3</returns>
private static double fade(double t)
{
return t * t * t * (t * (t * 6 - 15) + 10); // 6t^5 - 15t^4 + 10t^3
}
/// <summary>
/// To my knowledge essential method to calculate Perlin Noise values.
/// </summary>
/// <remarks>
/// This method used to be public in the original GitHub project, but because I see no use in directly accessing this method.
/// </remarks>
/// <param name="a">parameter.</param>
/// <param name="b">parameter.</param>
/// <param name="x">parameter.</param>
/// <returns>a + x * (b - a)</returns>
private static double lerp(double a, double b, double x)
{
return a + x * (b - a);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment