Skip to content

Instantly share code, notes, and snippets.

@wmealing
Created May 4, 2026 19:09
Show Gist options
  • Select an option

  • Save wmealing/0ee079b5a88ece9d3cbb161285b00731 to your computer and use it in GitHub Desktop.

Select an option

Save wmealing/0ee079b5a88ece9d3cbb161285b00731 to your computer and use it in GitHub Desktop.
-module(perlin).
-export([noise/3, noise/4, octave/5]).
%% Ken Perlin's permutation table
-define(PERMUTATION, [
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36,
103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75,
0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149,
56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27,
166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76,
132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100,
109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118,
126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155,
167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178,
185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191,
179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181,
199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138,
236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
156, 180
]).
%% ------------------------------------------------------------------
%% API
%% ------------------------------------------------------------------
noise(X, Y, Z) ->
noise(X, Y, Z, -1).
noise(XIn, YIn, ZIn, Repeat) ->
%% Build the 512-tuple permutation table for lookup
P = get_permutation_tuple(),
X = maybe_fmod(XIn, Repeat),
Y = maybe_fmod(YIn, Repeat),
Z = maybe_fmod(ZIn, Repeat),
Xi = floor(X) band 255,
Yi = floor(Y) band 255,
Zi = floor(Z) band 255,
Xf = X - floor(X),
Yf = Y - floor(Y),
Zf = Z - floor(Z),
U = fade(Xf),
V = fade(Yf),
W = fade(Zf),
%% 0-based index vs 1-based index:
%% Elixir's elem(tuple, idx) is 0-based. Erlang's element(idx, tuple) is 1-based.
%% We add 1 to the hashes to use as tuple indices.
AAA = element(element(element(Xi + 1, P) + Yi + 1, P) + Zi + 1, P),
ABA = element(element(element(Xi + 1, P) + inc(Yi, Repeat) + 1, P) + Zi + 1, P),
AAB = element(element(element(Xi + 1, P) + Yi + 1, P) + inc(Zi, Repeat) + 1, P),
ABB = element(element(element(Xi + 1, P) + inc(Yi, Repeat) + 1, P) + inc(Zi, Repeat) + 1, P),
BAA = element(element(element(inc(Xi, Repeat) + 1, P) + Yi + 1, P) + Zi + 1, P),
BBA = element(element(element(inc(Xi, Repeat) + 1, P) + inc(Yi, Repeat) + 1, P) + Zi + 1, P),
BAB = element(element(element(inc(Xi, Repeat) + 1, P) + Yi + 1, P) + inc(Zi, Repeat) + 1, P),
BBB = element(element(element(inc(Xi, Repeat) + 1, P) + inc(Yi, Repeat) + 1, P) + inc(Zi, Repeat) + 1, P),
X1 = lerp(grad(AAA, Xf, Yf, Zf), grad(BAA, Xf - 1.0, Yf, Zf), U),
X2 = lerp(grad(ABA, Xf, Yf - 1.0, Zf), grad(BBA, Xf - 1.0, Yf - 1.0, Zf), U),
Y1 = lerp(X1, X2, V),
X1_2 = lerp(grad(AAB, Xf, Yf, Zf - 1.0), grad(BAB, Xf - 1.0, Yf, Zf - 1.0), U),
X2_2 = lerp(grad(ABB, Xf, Yf - 1.0, Zf - 1.0), grad(BBB, Xf - 1.0, Yf - 1.0, Zf - 1.0), U),
Y2 = lerp(X1_2, X2_2, V),
(lerp(Y1, Y2, W) + 1.0) / 2.0.
octave(X, Y, Z, Octaves, Persistence) ->
{Total, MaxValue, _, _} = lists:foldl(
fun(_, {AccTotal, AccMax, Freq, Amp}) ->
NewTotal = AccTotal + noise(X * Freq, Y * Freq, Z * Freq) * Amp,
{NewTotal, AccMax + Amp, Freq * 2.0, Amp * Persistence}
end,
{0.0, 0.0, 1.0, 1.0},
lists:seq(0, Octaves - 1)
),
Total / MaxValue.
%% ------------------------------------------------------------------
%% Internal Functions
%% ------------------------------------------------------------------
get_permutation_tuple() ->
%% Duplicate the list to 512 items and convert to tuple
List = ?PERMUTATION ++ ?PERMUTATION,
list_to_tuple(List).
maybe_fmod(Val, Repeat) when Repeat > 0 -> math:fmod(Val, Repeat);
maybe_fmod(Val, _) -> Val.
fade(T) -> T * T * T * (T * (T * 6.0 - 15.0) + 10.0).
lerp(A, B, X) -> A + X * (B - A).
inc(Int, Repeat) ->
NewInt = Int + 1,
if
Repeat > 0 -> NewInt rem Repeat;
true -> NewInt
end.
grad(Hash, X, Y, Z) ->
case Hash band 16#F of
16#0 -> X + Y;
16#1 -> -X + Y;
16#2 -> X - Y;
16#3 -> -X - Y;
16#4 -> X + Z;
16#5 -> -X + Z;
16#6 -> X - Z;
16#7 -> -X - Z;
16#8 -> Y + Z;
16#9 -> -Y + Z;
16#A -> Y - Z;
16#B -> -Y - Z;
16#C -> Y + X;
16#D -> -Y + Z;
16#E -> Y - X;
16#F -> -Y - Z;
_ -> 0.0
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment