Last active
March 31, 2022 14:06
-
-
Save jakergrossman/bb6e8f50dde66e7a877630defd6d4b94 to your computer and use it in GitHub Desktop.
Animated Perlin Noise with MATLAB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo was created with the following parameters: