One of my Go projects uses procedurally-generated terrain — which means I want smooth random noise. Perlin noise is a common choice, and there are existing Go implementations, but Perlin noise suffers from axis-aligned artifacts. Ken Perlin's successor to Perlin noise (Simplex) fixes those problems, but adds a worse one: it's patented. Thankfully, there's an alternative: OpenSimplex noise by Kurt Spencer. I couldn't find a Go version, so I've ported OpenSimplex noise to Go, and released it under the same Unlicense as the OpenSimplex algorithm.
Besides getting the code to compile and look basically right, my goal was to get identical output to the reference implementation — ideally, regardless of platform. My first attempt at testing was to render and compare greyscale PNGs of the noise. That caught the first few glaring bugs, but it failed at the larger goal of correctness: I was crushing 64-bit floats into a tiny color channel.
I needed to preserve all 64-bits of the inputs and output. I also wanted less uniform test samples and a wider range of input, in case there were subtle bugs masked by the regular interval and all-positive numbers. For the second pass, I generated 2048 samples per dimension, randomly choosing coordinates in [-1000, 1000] from the reference implementation.
Now that my test failures were in terms of
float64s, I found my final
bug: everything was off by around 0.00000000001. The problem was with my very
first choice when porting — high-precision Go constants.
The OpenSimplex Java code starts with a few lines like this:
double STRETCH_CONSTANT_2D = -0.211324865405187; //(1/Math.sqrt(2+1)-1)/2; double SQUISH_CONSTANT_2D = 0.366025403784439; //(Math.sqrt(2+1)-1)/2; double STRETCH_CONSTANT_3D = -1.0 / 6; //(1/Math.sqrt(3+1)-1)/3; double SQUISH_CONSTANT_3D = 1.0 / 3; //(Math.sqrt(3+1)-1)/3; double STRETCH_CONSTANT_4D = -0.138196601125011; //(1/Math.sqrt(4+1)-1)/4; double SQUISH_CONSTANT_4D = 0.309016994374947; //(Math.sqrt(4+1)-1)/4;
I jumped at the chance to reevaluate the functions and pile on as much
precision as I could. Go distinguishes itself from most languages with
high-precision compile-time math, but this port needs consistency over
precision. Restoring the original constants did the unthinkable: I compared
floating point numbers with
== and passed tests.
I have two long-term goals for this code: testing on other architectures (to look for floating-point math surprises), and benchmarking. It can take a lot of samples of noise to generate a procedural scene, so speed is important; It's also an opportunity to get more experience with Go's built-in benchmarking tools. Even without those improvements, I think this code is still useful enough to publish; OpenSimplex is great for anyone who wants higher quality than Perlin noise, but isn't willing to deal with patents.