SimplexNoise
A fast simplex noise implementation in Elixir supporting 2D, 3D, and 4D noise generation with deterministic seeding.
This is a faithful port of the simplex-noise npm package by Jonas Wagner. Given the same seed and PRNG, it produces bit-identical output to the JS library.
Installation
Add simplex_noise to your list of dependencies in mix.exs:
def deps do
[
{:simplex_noise, "~> 0.1.0"}
]
endUsage
The API follows a two-step pattern: create a noise state with a seed, then sample it at any coordinates. All noise functions return a float in [-1, 1].
# 2D noise
state = SimplexNoise.create_noise_2d(42)
SimplexNoise.noise2d(state, 1.0, 2.0)
# => -0.4812...
# 3D noise
state = SimplexNoise.create_noise_3d(42)
SimplexNoise.noise3d(state, 1.0, 2.0, 3.0)
# 4D noise
state = SimplexNoise.create_noise_4d(42)
SimplexNoise.noise4d(state, 1.0, 2.0, 3.0, 4.0)
# Batch APIs (higher throughput for field generation)
SimplexNoise.noise2d_many(state, [{0.0, 0.0}, {0.125, 0.25}, {0.25, 0.5}])Custom PRNG
Instead of an integer seed, you can pass a zero-arity function that returns floats in [0, 1). This matches the JS library's API which accepts a random function.
state = SimplexNoise.create_noise_2d(fn -> :rand.uniform() end)
SimplexNoise.noise2d(state, 1.0, 2.0)Seeding
When an integer seed is provided, a built-in mulberry32 PRNG generates the permutation table, ensuring deterministic output. The same seed always produces the same noise values.
Project Structure
lib/
simplex_noise.ex # Core module — noise state creation, 2D/3D/4D sampling
simplex_noise/prng.ex # Mulberry32 PRNG (internal, JS-compatible 32-bit arithmetic)
test/
simplex_noise_test.exs # Perm table parity, 2D golden vectors, property tests, PRNG tests
simplex_noise/noise3d_test.exs # 3D golden vectors + property tests
simplex_noise/noise4d_test.exs # 4D golden vectors + property tests
fixtures/ # JSON golden vectors generated from the JS libraryTesting
mix testTests include:
- Golden vector tests — output is compared against values produced by the JS
simplex-noiselibrary to verify bit-level parity. - Property-based tests (via
stream_data) — noise output is always within[-1, 1], and identical seeds produce identical results.
Benchmarks
Each scenario evaluates 512 noise calls (8x8x8 grid) per iteration, matching the methodology of the upstream JS library's perf/index.js.
For best throughput on BEAM, prefer batch APIs (noise2d_many/2, noise3d_many/2, noise4d_many/2) over calling scalar noise* in Enum loops.
Elixir (BEAM JIT)
Run with mix run bench/simplex_noise_bench.exs:
Run both Elixir + JS benchmarks and refresh these README tables with:
mix bench.update_readme
| Scenario | IPS | Avg per batch | Median |
|---|---|---|---|
| noise2d_many (512 calls) | 8.98 K | 111.33 us | 79.46 us |
| noise3d (512 calls) | 6.81 K | 146.80 us | 126.75 us |
| noise2d (512 calls) | 6.67 K | 149.87 us | 75 us |
| noise4d (512 calls) | 4.46 K | 224.12 us | 208.88 us |
| noise3d_many (512 calls) | 4.35 K | 229.96 us | 138.25 us |
| noise4d_many (512 calls) | 2.75 K | 363.10 us | 314.92 us |
JavaScript (Node.js / V8 JIT)
Run with cd bench/js && npm install && npm run bench:
| Scenario | iterations/s | noise calls/s | avg per batch |
|---|---|---|---|
| noise2D (512 calls) | 11,403 | 5,838,413 | 87.70 μs |
| noise3D (512 calls) | 12,764 | 6,535,316 | 78.34 μs |
| noise4D (512 calls) | 10,663 | 5,459,224 | 93.79 μs |
Comparison
V8's JIT is heavily optimized for tight numerical loops, so the JS library is still faster in raw single-threaded scalar throughput. The Elixir implementation improves total generation throughput by using batch APIs, dropping 512-point generation to ~111.33us (2D), ~229.96us (3D), and ~363.10us (4D), and benefits from BEAM concurrency when computing multiple regions in parallel.
Machine: Apple M2, 8 cores, macOS. Elixir 1.18.4 / OTP 28.0.2, Node.js v22.17.0. Full Benchee output is saved to
bench/output/results.md.
License
MIT