Gradient noise class based on Simplex noise

Here's a seedable 2D and 3D gradient noise class in C# that I wrote based on Simplex noise. Seems to work properly (even though it's highly experimental). I'm looking for tips to improve the quality of the code and performance of the code (it's already quite well optimized).

Note: I've tried calculating some reusable values in the 2D and 3D noise functions, but that only seems to slow things down for some odd reason.

Edit: I'm particularly interested in the quality of the hash function I'm using in relation it's use case.

using System;

//
// Simple seedable 2D and 3D noise class based on Simplex noise.
//

public class SimpleNoise
{
public ulong seed;

private ulong internalSeed;

private double[] gradient3DxData = { 1, 0, -1, 0, 1, 0, -1, 0, 1, 1, -1, -1, 1, -1, 0, 0 };
private double[] gradient3DyData = { 0, 1, 0, -1, 0, 1, 0, -1, -1, 1, 1, -1, 0, 0, 1, -1 };
private double[] gradient3DzData = { 1, 1, 1, 1, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, -1, -1 };

// constructor

public SimpleNoise(ulong seed)
{
// seed xorshift* prng

if (seed != 0)
internalSeed = seed;

else
internalSeed = (ulong)DateTime.Now.Ticks;

if (internalSeed == 0) // won't happen until 23:59:59 December 31, 9999
internalSeed = 1;

this.seed = internalSeed;

// fill x and y arrays

for (int i = 0; i < 128; i++)
{
gradient2Dx[i + 000] = (i & 15) / 7.5 - 1;
gradient2Dx[i + 128] = (i & 15) / 7.5 - 1;

gradient2Dy[i + 000] = 1 - a;
gradient2Dy[i + 128] = a - 1;
}

// shuffle arrays

for (uint i = 255; i >= 1; i--)
{
uint j = GenUInt(i + 1);

}

// fill x, y and z arrays

for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
{
}
}

// shuffle arrays

for (uint i = 255; i >= 1; i--)
{
uint j = GenUInt(i + 1);

}
}

// xorshift* prng

private void Mix()
{
internalSeed ^= (internalSeed >> 12);
internalSeed ^= (internalSeed << 25);
internalSeed ^= (internalSeed >> 27);
internalSeed *= 2685821657736338717;
}

// generate random integer

private uint GenUInt(uint mod)
{
Mix();

return (uint)internalSeed % mod;
}

//
// 2D noise
//
// range for x and y is -2^32 to 2^32 (is actually larger, but shouldn't be needed)
//

public unsafe double Noise(double x, double y)
{
long i = (long)(x + 0x100000001);
long j = (long)(y + 0x100000001);

long hash1 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483)) >> 48) & 255;
long hash2 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483)) >> 48) & 255;
long hash3 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483)) >> 48) & 255;
long hash4 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483)) >> 48) & 255;

double x1 = (x + 0x100000001) - i;
double y1 = (y + 0x100000001) - j;

double x2 = x1 - 1;
double y2 = y1 - 1;

double xx1 = x1 * x1 + y1 * y1 - 1;
double xx2 = x2 * x2 + y1 * y1 - 1;
double xx3 = x1 * x1 + y2 * y2 - 1;
double xx4 = x2 * x2 + y2 * y2 - 1;

return
(*(ulong*)&xx1 >> 63) * xx1 * xx1 * xx1 * xx1 * (gradient2Dx[hash1] * x1 + gradient2Dy[hash1] * y1) +
(*(ulong*)&xx2 >> 63) * xx2 * xx2 * xx2 * xx2 * (gradient2Dx[hash2] * x2 + gradient2Dy[hash2] * y1) +
(*(ulong*)&xx3 >> 63) * xx3 * xx3 * xx3 * xx3 * (gradient2Dx[hash3] * x1 + gradient2Dy[hash3] * y2) +
(*(ulong*)&xx4 >> 63) * xx4 * xx4 * xx4 * xx4 * (gradient2Dx[hash4] * x2 + gradient2Dy[hash4] * y2);
}

//
// 3D noise
//
// range for x, y and z is -2^32 to 2^32 (is actually larger, but shouldn't be needed)
//

public unsafe double Noise(double x, double y, double z)
{
long i = (long)(x + 0x100000001);
long j = (long)(y + 0x100000001);
long k = (long)(z + 0x100000001);

long hash1 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483 * (k + 0))) >> 48) & 255;
long hash2 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483 * (k + 0))) >> 48) & 255;
long hash3 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483 * (k + 0))) >> 48) & 255;
long hash4 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483 * (k + 0))) >> 48) & 255;
long hash5 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483 * (k + 1))) >> 48) & 255;
long hash6 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483 * (k + 1))) >> 48) & 255;
long hash7 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483 * (k + 1))) >> 48) & 255;
long hash8 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483 * (k + 1))) >> 48) & 255;

double x0 = (x + 0x100000001) - i;
double y0 = (y + 0x100000001) - j;
double z0 = (z + 0x100000001) - k;

double x1 = x0 - 1;
double y1 = y0 - 1;
double z1 = z0 - 1;

double xx1 = x0 * x0 + y0 * y0 + z0 * z0 - 1;
double xx2 = x1 * x1 + y0 * y0 + z0 * z0 - 1;
double xx3 = x0 * x0 + y1 * y1 + z0 * z0 - 1;
double xx4 = x1 * x1 + y1 * y1 + z0 * z0 - 1;
double xx5 = x0 * x0 + y0 * y0 + z1 * z1 - 1;
double xx6 = x1 * x1 + y0 * y0 + z1 * z1 - 1;
double xx7 = x0 * x0 + y1 * y1 + z1 * z1 - 1;
double xx8 = x1 * x1 + y1 * y1 + z1 * z1 - 1;

return
(*(ulong*)&xx1 >> 63) * xx1 * xx1 * xx1 * xx1 * (gradient3Dx[hash1] * x0 + gradient3Dy[hash1] * y0 + gradient3Dz[hash1] * z0) +
(*(ulong*)&xx2 >> 63) * xx2 * xx2 * xx2 * xx2 * (gradient3Dx[hash2] * x1 + gradient3Dy[hash2] * y0 + gradient3Dz[hash2] * z0) +
(*(ulong*)&xx3 >> 63) * xx3 * xx3 * xx3 * xx3 * (gradient3Dx[hash3] * x0 + gradient3Dy[hash3] * y1 + gradient3Dz[hash3] * z0) +
(*(ulong*)&xx4 >> 63) * xx4 * xx4 * xx4 * xx4 * (gradient3Dx[hash4] * x1 + gradient3Dy[hash4] * y1 + gradient3Dz[hash4] * z0) +
(*(ulong*)&xx5 >> 63) * xx5 * xx5 * xx5 * xx5 * (gradient3Dx[hash5] * x0 + gradient3Dy[hash5] * y0 + gradient3Dz[hash5] * z1) +
(*(ulong*)&xx6 >> 63) * xx6 * xx6 * xx6 * xx6 * (gradient3Dx[hash6] * x1 + gradient3Dy[hash6] * y0 + gradient3Dz[hash6] * z1) +
(*(ulong*)&xx7 >> 63) * xx7 * xx7 * xx7 * xx7 * (gradient3Dx[hash7] * x0 + gradient3Dy[hash7] * y1 + gradient3Dz[hash7] * z1) +
(*(ulong*)&xx8 >> 63) * xx8 * xx8 * xx8 * xx8 * (gradient3Dx[hash8] * x1 + gradient3Dy[hash8] * y1 + gradient3Dz[hash8] * z1);
}
}


The main problems of your code is a lot of magic numbers and code repetition. What is 256? 128? (i & 15) / 7.5 - 1? Looking on your code other people should easily understand what it does.

private const int GradientSize = 256;



Here

gradient2Dx[i + 000] = (i & 15) / 7.5 - 1;
gradient2Dx[i + 128] = (i & 15) / 7.5 - 1;


the magic expression repeated twice. Why not to store it to local variable:

var gradientValue = (i & 15) / 7.5 - 1;


Also, can you say what purpose of + 000? :)

You perform swapping of array elements many times and repeat the same code in all these places (loops commented as shuffle arrays). Define the Swap method and use it:

private static void Swap(double[] array, uint i, uint j)
{
var tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}


for (uint i = GradientSize - 1; i >= 1; i--)
{
uint j = GenUInt(i + 1);

}


and

for (uint i = GradientSize - 1; i >= 1; i--)
{
uint j = GenUInt(i + 1);

}


I can't believe that looking on this code

long hash1 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483)) >> 48) & 255;
long hash2 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 0) ^ 3842148274728412483)) >> 48) & 255;
long hash3 = ((6082394749206781697 * (1732050807568877293 * (i + 0) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483)) >> 48) & 255;
long hash4 = ((6082394749206781697 * (1732050807568877293 * (i + 1) ^ 8650415921358664919 * (j + 1) ^ 3842148274728412483)) >> 48) & 255;


you have no thoughts to rewrite it by eliminating repeated calculations and name all those constants :)

• Good suggestions. Swap() and the big prime constants are so obvious. However, I have no clue about how to rewrite the repeating calculations in the two noise functions without making the functions slower. For example, I tried putting them in a function (which yields much nicer code), but the execution time goes up by almost 60 percent. Aug 24, 2017 at 6:34
• How did you measures? Did you try to "warm up" JIT compiler to give it a chance to inline your methods? Aug 24, 2017 at 6:46
• I measured it with the stopwatch function from Mono. Don't know what you mean by warming up the JIT, but Mono seems to refuse to inline the functions, and there seems to be no way to force inlining in the the Mono 3.5 compiler (Unity3D) either. Aug 24, 2017 at 6:50
• Yes, there is no way to force to inline methods but can you try to execute your routine, for example, 10 times and measure execution time? How the execution time will be changed with each call? :) Aug 24, 2017 at 7:07
• The goal is straightforward: Performance critical parts should be as fast as possible, while everything else should be readable. Luckily there usually aren't that many performance critical parts in my project, so most of the code can be written to be easy to read. Aug 24, 2017 at 8:25