Seeded Perlin, value and cellular noise in 2D and 3D, with fractal layers and domain warping.
Import blitz.noise As N gives your program these, with N. in front. Each name leads to its part of the reference.
CreateNoise:Noise(seed:Long, options:NoiseOptions)Sample2:Double(noise:Noise, x:Double, y:Double)Sample3:Double(noise:Noise, x:Double, y:Double, z:Double)Warp2(noise:Noise, x:Double, y:Double, amount:Double, output:Point2)NoiseOptionskind:Int = NoiseKind.Perlin, octaves:Int = 1, persistence:Double = 0.5D, lacunarity:Double = 2DPoint2x:Double, y:DoubleNoiseNoiseKindPerlin, Value, CellularNoise makes smooth randomness: numbers that change gently from place to place,
instead of jumping about the way Rnd does. Use it for hills and valleys,
coastlines, clouds, or a torch that flickers. Give it a seed and a position,
and it gives back a number. The same seed and position always give the same
number, so you can make a whole world again from its seed instead of saving
it.

Open world.bb and run it. Press Space to make a new world from the next seed.
; A map of a made-up world, coloured by height: sea, sand, grass, forest, rock
; and snow. Space makes a new world from the next seed. Escape quits.
Dialect "modern"
Import blitz.noise As N
Const SPOT:Int = 5 ; Each spot on the map is 5 pixels square.
Graphics 640, 480, 0, 2
SetBuffer BackBuffer()
ClsColor 18, 52, 79
; Four layers of noise: big shapes first, then finer and finer detail.
Local options:N.NoiseOptions = New N.NoiseOptions
options.octaves = 4
Local seed:Long = 1L
Local land:N.Noise = Null
Local picture:Int = CreateImage(120 * SPOT, 84 * SPOT)
Local changed:Int = True
Local x:Int
Local y:Int
While Not KeyDown(1)
If KeyHit(57) ; Space
seed = seed + 1L
changed = True
EndIf
If changed
; The same seed always makes the same world.
Delete land
land = N.CreateNoise(seed, options)
; Colour the map once, into a picture, rather than every frame.
SetBuffer ImageBuffer(picture)
For y = 0 To 83
For x = 0 To 119
; Dividing by 30 sets the size of the land. Smaller numbers
; make smaller islands.
Local height:Double = N.Sample2(land, Double(x) / 30D, Double(y) / 30D)
; Heights stay close to 0, so the bands are narrow.
If height < -0.12D
Color 25, 70, 140 ; Deep sea
ElseIf height < 0D
Color 45, 110, 180 ; Shallow sea
ElseIf height < 0.03D
Color 220, 205, 145 ; Sand
ElseIf height < 0.12D
Color 95, 160, 70 ; Grass
ElseIf height < 0.19D
Color 45, 115, 55 ; Forest
ElseIf height < 0.25D
Color 130, 120, 110 ; Rock
Else
Color 240, 240, 245 ; Snow
EndIf
Rect x * SPOT, y * SPOT, SPOT, SPOT, True
Next
Next
SetBuffer BackBuffer()
changed = False
EndIf
Cls
DrawImage picture, 20, 20
Color 255, 255, 255
Text 20, 448, "Seed " + seed + " Space: a new world"
Flip
Wend
FreeImage picture
Delete land
Delete options
End
Each spot on the map asks the noise for a height at its position, and the height picks its colour. Spots next to each other get similar heights, so the land comes out in smooth shapes rather than speckles. The map is coloured once for each world, not every frame, because sampling ten thousand spots takes a moment.
Divide the positions by a bigger number for bigger shapes, or a smaller one for smaller shapes. The noise has about one bump per unit, so dividing by 30 spreads each bump over about 30 spots.
Local height:Double = N.Sample2(land, Double(x) / 80D, Double(y) / 80D) ; Continents.
Each layer, or octave, adds finer detail on top of the ones before.
persistence says how much each layer counts compared with the one before it,
from 0 to 1, and lacunarity how much finer it is.
Local options:N.NoiseOptions = New N.NoiseOptions options.octaves = 6 ; More layers, more detail. options.persistence = 0.6D ; Rougher. Lower is smoother. Local rough:N.Noise = N.CreateNoise(42L, options) Delete options ; The noise keeps its own copy.
Set each point of a terrain from the noise. Terrain heights go from 0 to 1, so add a half.
Local ground:Int = CreateTerrain(128) ; A 128 by 128 grid.
ScaleEntity ground, 4, 60, 4 ; Wider, and much taller.
Local x:Int
Local z:Int
For z = 0 To 127
For x = 0 To 127
Local height:Double = N.Sample2(land, Double(x) / 30D, Double(z) / 30D)
ModifyTerrain ground, x, z, Float(height + 0.5D)
Next
Next
Sample along a line as time goes by. The light's brightness then wobbles smoothly, instead of jumping.
Local seconds:Double = Double(MilliSecs() - startTime) / 1000D Local flicker:Double = N.Sample2(land, seconds * 3D, 0.5D) ; Between -0.5 and 0.5. LightColor torch, 200 + 100 * flicker, 150 + 80 * flicker, 80
Give each torch a different second number, such as 1.5 and 2.5, so they don't
all flicker together. For clouds that drift and change, use Sample3, with
the time as the third number.
Choose another kind of noise. Value noise is blotchier, and uses more of the
range from -1 to 1. Cellular noise is the distance to the nearest of a
scatter of points, from 0 to 1, which looks like cells, stones or scales.
options.kind = N.NoiseKind.Cellular
Warp each position with the noise before you sample it. Coastlines and clouds come out twisted.
Local warped:N.Point2 = New N.Point2 ; Make this once, and use it again. N.Warp2 land, Double(x) / 30D, Double(y) / 30D, 2D, warped ; Move it by up to 2. Local height:Double = N.Sample2(land, warped.x, warped.y)
Blip's tip
Perlin noise, the usual kind, doesn't use the whole range from -1 to 1.
Sample2never goes beyond -0.5 or 0.5, and the more layers there are, the closer to 0 it stays. Choose your heights for sea, sand and snow by trying them, as the program above does.
MilliSecs(), which can be very big.Rnd. Making and sampling noise never changes what
Rnd and Rand give the rest of your game.Point2.Three more complete programs come with the module:
The reference below has every function, option and rule.
0.1.0 local preview; unpublished. Perlin gradient, value and nearest-feature
cellular noise in 2D/3D, normalised fractal layers and 2D domain warp. Pure numeric
sampling changes no world or global random state. Requires blitz.random 0.1.0
and Extended object-lifecycle-v1 (runtime 1114 / module ABI 5 or later).
Blitz3D+ comes with this module and blitz.random, so any Extended program can
Import blitz.noise. Open an example and run it.
Dialect "modern" Import blitz.noise Local land:Noise.Noise = Noise.CreateNoise(42L, Null) Print Noise.Sample2(land, 0.25D, -0.75D) Print Noise.Sample3(land, 0.25D, -0.75D, 1.125D) Delete land End
The second argument is required but nullable. Actual compiler testing found
that object parameters cannot default to Null; the plan's default was design
notation. Pass explicit Null for defaults, or a caller-owned options object.
| Declaration | Contract and example |
|---|---|
NoiseKind | Int enum: Perlin=0, Value=1, Cellular=2. Other values are rejected. |
NoiseOptions | Public fields kind:Int=Perlin, octaves:Int=1, persistence:Double=0.5D, lacunarity:Double=2D. New Noise.NoiseOptions; caller deletes it. |
Noise | Opaque field owning one 256-entry Int List. Private constructor/state/helpers. Delete land releases the table. |
CreateNoise(seed:Long, options:NoiseOptions):Noise | Caller owns result. Copies options; never retains the options object or any random stream. Validates before allocation. Noise.CreateNoise(42L, Null). |
Sample2(noise, x:Double, y:Double):Double | Scalar sample in noise space, no allocation. Noise.Sample2(land, Double(x)/64D, Double(y)/64D). |
Sample3(noise, x:Double, y:Double, z:Double):Double | True 3D field with the same contract; not promised equal to Sample2 at z=0. |
Point2 | Public Double x and y, initially zero; caller-owned reusable warp output. New Noise.Point2. |
Warp2(noise, x:Double, y:Double, amount:Double, output:Point2) | Borrows output and writes both coordinates, no allocation. Noise.Warp2(land, x, y, 3D, point) then Noise.Sample2(land, point.x, point.y). |
Local options:Noise.NoiseOptions = New Noise.NoiseOptions options.kind = Noise.NoiseKind.Value options.octaves = 4 options.persistence = 0.5D options.lacunarity = 2D Local clouds:Noise.Noise = Noise.CreateNoise(2026L, options) Delete options ; Safe immediately: construction copied the values. Local point:Noise.Point2 = New Noise.Point2 Noise.Warp2 clouds, 0.25D, -0.75D, 2D, point Print Noise.Sample2(clouds, point.x, point.y) Delete point Delete clouds
Octaves must be 1..16, persistence finite in 0..1, lacunarity finite and >=1.
Zero persistence is a valid single effective octave, even with 16 configured
layers. All sample coordinates must be finite and every effective octave
coordinate must lie in [-1048576,1048576], inclusive. The check happens before
flooring or integer conversion. Scale world coordinates yourself. For example,
4 layers with lacunarity 2 require base coordinates within +/-131072. Invalid
options, Null/deleted objects, non-finite inputs or coordinates beyond the bound
raise contextual RuntimeError. There are no recoverable factory/I/O failures,
so no redundant LastError() API. options.octaves = 0 is a programming error.
Warp uses two independently salted versions of the configured field. Signed fields displace directly; cellular is mapped from [0,1] to [-1,1] first. Each axis moves by at most Abs(amount), with finite negative amounts reversing displacement. Zero amount preserves the original coordinate bits, including negative zero, without evaluating higher octaves. The output is updated only after both coordinates pass validation. A later Sample2 still checks its own octave limits.
Delete is explicit; scope exit/assigning Null does not run the destructor. Tables and the construction-only stream are owned by noise, and never exposed. Options and Point2 always remain caller-owned. Independent fields survive each other's deletion. Repeated typed Null/deleted aliases are safe to Delete.
Save this identifier together with seed, all options and coordinate scaling when saving a procedural world. Seed tables, gradient rules and feature placement are part of world compatibility; changing them requires an incompatible version or preserving this implementation. There is no Worker/thread-safety promise.
blitz.random PCG32, the full seed
bit pattern and sequence 1. Release that stream immediately. There is exactly
one fixed-size table configuration, no global permutation and no per-sample
allocation. Construction is O(256); samples are O(effective octaves).p[(p[(p[x&255]+y)&255]+z)&255], then look up
p[(hash+salt)&255]. Base samples use salt 0, warp axes use 73 and 151.
For 2D, z=0. Lattice coordinates use FloorD, including negative positions.
The single-octave field repeats every 256 lattice units on each axis. Layering
with non-integer lacunarity does not preserve that common period. There is no
arbitrary tile-size API.6t^5-15t^4+10t^3. 2D uses four diagonal gradients;
3D uses the twelve cube-edge gradients and the four padded directions from
Perlin's paper. Dot products are halved, a conservative [-1,1] normalisation;
do not expect them to fill the whole interval. Corners are bilinearly or
trilinearly interpolated. Value noise instead maps each hash to
hash/127.5-1, with the same fade. Both are continuous with smooth cell faces.(p[(h+85*a)&255]+0.5)/256. Search expanding cell shells, skip boxes whose
lower distance cannot win, stop when all unvisited cells are farther than
the best feature. An own-cell feature is within sqrt(dimensions), proving
termination by radius 2. Normalise Euclidean distance by sqrt(dimensions),
returning [0,1]. It is continuous but may have derivative discontinuities;
it does not have the same distribution as gradient noise.Use a release build for generation workloads: debug source instrumentation can dominate these tight numeric loops.
Tileable noise, 3D warp and Worker adapters are future features, not incomplete switches. See provenance and licence.