Introduction
A while back I made a handy tool for procedural generation / noise based mechanics! Been meaning to share it for a while. To understand it though - you need to understand two prior bits of Roblox / lua technology.
math.noise
This is math.noise:
local value = math.noise(x,y)
when plotting this value across a 2d plane it looks like this
it’s really handy for things like terrain generation, adding complexity to textures, and many different forms of procedural generation.
Random Class
This is the Random Class:
local seed = 3
local random = Random.new(seed)
local value = random:NextNumber() -- returns a rand number from 0-1
It’s better in many ways than math.random because you can seed specific instances of it. This means that you can have it send the same random numbers in the same random order. This is called being “deterministic” and is very helpful for when you want to synchronize two different systems (or player clients).
There is a function called math.randomseed traditionally in lua, however due to it being a global seed it can fail to remain deterministic pretty easily when multiple threads call it.
Introducing: Noise Class
This is where my class comes in. It takes the deterministic elegance of the Random
Class, and combines it with the utility provided by math.noise
!
Currently, it supports 4 different noise maps in both 2D and 3D:
Random
Perlin (exact same as math.noise, but now deterministic!)
Worley / Cellular
Voronoi
Here’s how you might use the perlin one:
local noise = Noise.new(12356)
local value = noise:Perlin(x,y) -- a value from 0 to 1 (ish)
reminder, this is the same as:
local value = math.noise(x,y) -- also a value from 0 to 1 (ish)
except of course - with that seed number you will get the exact same random numbers in the exact same order using Noise.
Benchmarks
So - how fast do we compare to math.noise
?
Each map had the generation function called 32x32=1024 times, with the benchmark measuring how long it took to run to completion. You can find the bench script here if you want to reproduce it.
The math.noise
function using just an x and y parameter is the fastest. If you don’t need determinism and value speed, use that. It runs at around ~5000 operations/ops per millisecond/ms.
Compared to math.noise
:
- perlin 2D: 2x slower at ~2000 ops/ms.
- random 2D: 4.5x slower at ~1000 ops/ms.
- celullar 2D: 6x slower at ~800 ops/ms.
- voronoi 2D: 6.5x slower at ~770 ops/ms.
3D in general tends to slow things by 50-100% on average (except for Random, which is as low as <30% due to each dimension being solved independently). Thankfully, there are way more use cases for 2D than 3D in my experience.
As I find new optimizations I’ll of course add them, however these operate fast enough for most of my use cases - and realistically, the bottleneck will usually be when you update the instances to match the noise values, not the noise itself.
Where to Get It
- add it via the Wally Package Manager!
- Here’s the project itself on github.
- If github scares you, just copy the code from this page
- You can also download an rbxl with the code deployed here.
Contributing
Would love to see some new noise maps added, as well as optimizations to the prior ones (except for Perlin - that’s literally a translation of the real math.noise
code from C++ to luau for ez language perf comparison).
That’s all! Thank y’all for reading and I hope this helps ya with your projects!