3
\$\begingroup\$

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[] gradient2Dx;
    private double[] gradient2Dy;

    private double[] gradient3Dx;
    private double[] gradient3Dy;
    private double[] gradient3Dz;

    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;

        // 2D gradients

        gradient2Dx = new double[256];
        gradient2Dy = new double[256];

        // 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;

            double a = Math.Abs(gradient2Dx[i]);

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

        // shuffle arrays

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

            double a = gradient2Dx[i];
            double b = gradient2Dy[i];

            gradient2Dx[i] = gradient2Dx[j];
            gradient2Dy[i] = gradient2Dy[j];

            gradient2Dx[j] = a;
            gradient2Dy[j] = b;
        }

        // 3D gradients

        gradient3Dx = new double[256];
        gradient3Dy = new double[256];
        gradient3Dz = new double[256];

        // fill x, y and z arrays

        for (int j = 0; j < 16; j++)
        {
            for (int i = 0; i < 16; i++)
            {
                gradient3Dx[i + (j << 4)] = gradient3DxData[i];
                gradient3Dy[i + (j << 4)] = gradient3DyData[i];
                gradient3Dz[i + (j << 4)] = gradient3DzData[i];
            }
        }

        // shuffle arrays

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

            double a = gradient3Dx[i];
            double b = gradient3Dy[i];
            double c = gradient3Dz[i];

            gradient3Dx[i] = gradient3Dx[j];
            gradient3Dy[i] = gradient3Dy[j];
            gradient3Dz[i] = gradient3Dz[j];

            gradient3Dx[j] = a;
            gradient3Dy[j] = b;
            gradient3Dz[j] = c;
        }
    }

    // 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);
    }
}
\$\endgroup\$
1
\$\begingroup\$

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;

gradient2Dx = new double[GradientSize];

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;
gradient2Dx[i + 000] = gradientValue;
gradient2Dx[i + 128] = gradientValue;

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;
}

Then your loops will be:

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

    Swap(gradient2Dx, i, j);
    Swap(gradient2Dy, i, j);
}

and

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

    Swap(gradient3Dx, i, j);
    Swap(gradient3Dy, i, j);
    Swap(gradient3Dz, i, j);
}

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 :)

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – Thorham Aug 24 '17 at 6:34
  • \$\begingroup\$ How did you measures? Did you try to "warm up" JIT compiler to give it a chance to inline your methods? \$\endgroup\$ – Maxim Aug 24 '17 at 6:46
  • \$\begingroup\$ 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. \$\endgroup\$ – Thorham Aug 24 '17 at 6:50
  • \$\begingroup\$ 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? :) \$\endgroup\$ – Maxim Aug 24 '17 at 7:07
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ – Thorham Aug 24 '17 at 8:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.