Skip to content

Instantly share code, notes, and snippets.

@jakergrossman
Last active March 31, 2022 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakergrossman/bb6e8f50dde66e7a877630defd6d4b94 to your computer and use it in GitHub Desktop.
Save jakergrossman/bb6e8f50dde66e7a877630defd6d4b94 to your computer and use it in GitHub Desktop.
Animated Perlin Noise with MATLAB
function [] = animated_perlin(min, max, segments, duration, options)
%% ANIMATED_PERLIN
% SYNTAX
% animated_perlin(min, max, segments, duration, Name, Value, ...)
%
% INPUTS:
% MIN Lower bound on data range in the X and Y dimensions.
%
% MAX Upper bound on data range in the X and Y dimensions.
%
% SEGMENTS The number of segments in the X and Y dimensions.
%
% DURATION The length of the generated animation, in seconds.
%
% OPTIONS: SAVE Whether or not to save the animation.
% Default: false
%
% FILENAME Name of the file to save to.
% Default: 'animated_perlin'
%
% PROFILE Video profile to use when saving.
% Default: 'MPEG-4'
%
% FRAMESPERSECOND Number of frames per second. Default: 30
%
% ZSCALE Scalar factor for Perlin noise in the
% range [0.0, 1.0]. Default: 1.
%
% ZDELTA Perlin Z difference per millisecond.
% Default: 0.001
%
% COLORMAP Colormap to use when drawing surface.
% Default: 'gray'
%
% SHADING Shading method to use when drawing surface.
% One of 'interp', 'flat', 'faceted'.
% Default: 'interp'
%
% FACEALPHA Opacity of the surface. Default: 1.0
%
% RENDERER Figure renderer to use. One of 'painters',
% 'opengl'. Default: 'opengl'
%
% MAXIMIZE Whether to maximize the figure. NOTE: Because
% getframe just captures the figure as it
% appears on the screen, this results in a
% higher quality output when saved.
% Default: false
%
% OUTPUT: NONE
%%% EXAMPLES
% animated_perlin(0, 4, 16, 5)
%
% animated_perlin(0, 4, 16, 5, 'FramesPerSecond', 60, 'Renderer', 'opengl')
%%%
% This is free and unencumbered software released into the public domain.
% Jake Grossman, 2022
arguments
min double
max double
segments uint64
duration double
% optional arguments
options.Save (1,1) logical = false
options.Filename (1,1) string = 'animated_perlin'
options.FramesPerSecond (1,1) {mustBePositive} = 30
options.Profile (1,1) string = 'MPEG-4';
options.ZScale (1,1) {mustBeInRange(options.ZScale, 0, 1)} = 1.0
options.ZDelta (1,1) {mustBeNumeric} = 0.001
options.ColorMap (1,1) string = 'gray'
options.Shading (1,1) string = 'interp'
options.FaceAlpha (1,1) {mustBeInRange(options.FaceAlpha, 0, 1)} = 1.0
options.Renderer (1,1) string = 'opengl'
options.Maximize (1,1) logical = true
end
assert(min <= max, 'Minimum %d must be less than or equal to maximum %s.')
isDeletedObj = @(h)isobject(h) & ~isgraphics(h);
frames = floor(duration * options.FramesPerSecond);
% ZDelta per frame based from ZDelta per millisecond and FramesPerSecond
frameZDelta = 1000 * options.ZDelta / options.FramesPerSecond;
%% make data
X = linspace(min, max, segments); Y = X;
frameData = zeros(frames, segments, segments);
start_t = datetime('now');
progress = waitbar(0, sprintf('Generating Frames (0/%d -- %ds)', frames, floor(seconds(datetime - start_t))));
for z = 1:frames
if isDeletedObj(progress)
% quit, don't save anything
return;
end
waitbar(z/frames, progress, sprintf('Generating Frames (%d/%d -- %ds)', z, frames, floor(seconds(datetime - start_t))));
d = perlin((z-1)*frameZDelta, Y, X);
frameData(z,:,:) = d;
end
delete(progress);
%% initialize video
if options.Save
video = VideoWriter(options.Filename, options.Profile);
video.FrameRate = options.FramesPerSecond;
open(video);
end
%% initialize figure
fig = figure('Renderer', options.Renderer, 'RendererMode', 'manual');
if options.Maximize, fig.WindowState = 'maximized'; end
if options.Save, disableDefaultInteractivity(gca); end
% create surface with dummy data and visual properties
s = surf(X, Y, zeros(segments, segments), 'FaceAlpha', options.FaceAlpha);
shading(options.Shading); zlim([-1 1]); colormap(options.ColorMap);
%% draw and save video frames
if options.Save
% disable tool and menu bar to avoid accidental tampering
set(fig, 'ToolBar', 'none'); set(fig, 'MenuBar', 'none');
start_t = datetime;
progress = waitbar(0, sprintf('Drawing/Saving Frames (0/%d -- %d)', frames, floor(seconds(datetime-start_t))));
limit = frames;
else
limit = inf;
end
f = 0;
while f < limit
f = f + 1;
if options.Save && isDeletedObj(progress) || isDeletedObj(s)
break
end
s.ZData = options.ZScale * reshape(frameData(mod(f, frames)+1, :, :), [], segments);
if options.Save
waitbar(f/frames, progress, sprintf('Drawing/Saving Frames (%d/%d -- %ds)', f, frames, floor(seconds(datetime-start_t))));
% write video frame
writeVideo(video, getframe(gcf));
else
pause(1/options.FramesPerSecond);
end
end
%% cleanup
if options.Save
close(video);
delete(progress);
end
delete(fig);
end
%% PERLIN
% 3D Perlin noise implementation based on Ken Perlin's Improved Noise
% reference implementation: https://cs.nyu.edu/~perlin/noise/
%
% SYNTAX
% perlin(zs, ys, xs)
% OR
% perlin(zs, ys, xs, perm)
%
% INPUTS:
%
% ZS, YS, XS Scalar or vectors representing the data points in the Z, Y,
% and X dimensions, respectively.
%
% PERM 256 element permutation to use for determining gradients.
% Defaults to Ken Perlin's original permutation
%
% OUTPUT:
%
% NOISE A matrix representing the resulting noise in l-major order.
% The output is squeezed to remove dimensions of size 1.
%
%
%% EXAMPLES
% perlin(0, 0, 0.25) % result: 0.1465
%
% perlin(0, 0, 0:1/3:1) % result: [0, 0.1235, -0.1235, 0]
%
% perlin (0, 0:1/4:1, 0:1/3:1) % result: [0 -0.1399 -0.2634 0 ;
% 0.1465 0.0066 -0.1169 0.1465 ;
% 0 -0.1399 -0.2634 0 ;
% -0.1465 -0.2864 -0.4099 -0.1465 ;
% 0 -0.1399 -0.2634 0 ]
%%
function noise = perlin(zs, ys, xs, perm)
arguments
zs {mustBeReal, mustBeVector}
ys {mustBeReal, mustBeVector}
xs {mustBeReal, mustBeVector}
perm {mustBeInteger, mustBeVector} = 0
end
% no value indicator
if perm == 0
% classical ken perlin permutation
perm = [ 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];
end
assert(length(perm) == 256, 'Permutation must have length 256');
p = repmat(perm, 1, 2);
noise = zeros([length(zs), length(ys), length(xs)]);
for i = 1:length(zs)
z = zs(i);
for j = 1:length(ys)
y = ys(j);
for k = 1:length(xs)
x = xs(k);
% unit cube that contains oint (x,y,z)
unitX = bitand(floor(x), 255);
unitY = bitand(floor(y), 255);
unitZ = bitand(floor(z), 255);
% relative coordinate within cube
cubeX = mod(x, 1);
cubeY = mod(y, 1);
cubeZ = mod(z, 1);
u = fade(cubeX);
v = fade(cubeY);
w = fade(cubeZ);
A = p(unitX+1)+unitY; AA = p(A+1)+unitZ; AB = p(A+2)+unitZ;
B = p(unitX+2)+unitY; BA = p(B+1)+unitZ; BB = p(B+2)+unitZ;
noise(i,j,k) = lerp(w, lerp(v, lerp(u, grad(p(AA+1), cubeX , cubeY , cubeZ ), ...
grad(p(BA+1), cubeX-1, cubeY , cubeZ )), ...
lerp(u, grad(p(AB+1), cubeX , cubeY-1, cubeZ ), ...
grad(p(BB+1), cubeX-1, cubeY-1, cubeZ ))), ...
lerp(v, lerp(u, grad(p(AA+2), cubeX , cubeY , cubeZ-1), ...
grad(p(BA+2), cubeX-1, cubeY , cubeZ-1)), ...
lerp(u, grad(p(AB+2), cubeX , cubeY-1, cubeZ-1), ...
grad(p(BB+2), cubeX-1, cubeY-1, cubeZ-1))));
end
end
end
% remove array dimensions of size 1
noise = squeeze(noise);
end
function [n] = fade(t)
n = t * t * t * (t * (t * 6 - 15) + 10);
end
function [n] = lerp(t, a, b)
n = a + t * (b-a);
end
function [n] = grad(hash, x, y, z)
h = bitand(hash, 15);
if h < 8
u = x;
else
u = y;
end
if h < 4
v = y;
else
if h == 12 | h == 14
v = x;
else
v = z;
end
end
if bitand(h,1) == 0, a=u; else, a=-u; end
if bitand(h,2) == 0, b=v; else, b=-v; end
n = a + b;
end
@jakergrossman
Copy link
Author

Demo was created with the following parameters:

animated_perlin(0, 4, 64, 3, 'FramesPerSecond', 60, 'ZScale', 0.3, 'Save', true)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment