Modified Joy division
Sand dunes inspired by Joy division album cover
Log in to post a comment.
// ═══════════════════════════════════════════════════════════════
// Sand Dunes — Joy Division style waves with asymmetric clustering
// Inspired/adapted from https://www.gorillasun.de/blog/smooth-curves-with-perlin-noise-and-recreating-the-unknown-pleasures-album-cover-in-p5
// Also inspired by the following turtles:
// https://turtletoy.net/turtle/ca12448d34
// https://turtletoy.net/turtle/f60c49e6f7
// https://turtletoy.net/turtle/fa14c628d4
// ═══════════════════════════════════════════════════════════════
// ── Tunable parameters ──────────────────
const numLines = 120; // min=40 max=170 step=1
const noiseFreqX = 0.04; // min=0.01 max=0.18 step=0.005
const noiseFreqY = 0.018; // min=0.005 max=0.09 step=0.005
const maxAmplitude = 28; // min=4 max=60 step=1
const clusterSide = 0.12; // min=0 max=1 step=0.01
const penOpacity = 0.72; // min=0.1 max=1.0 step=0.05
// ── Canvas & turtle setup ────────────────────────────────────
Canvas.setpenopacity(penOpacity);
const turtle = new Turtle();
turtle.penup();
// ── Perlin noise (smooth gradient noise, 2-D, multi-octave) ──
// I used AI to help generate pseudocode adapted from http://mrl.nyu.edu/~perlin/noise/ which I then reworked
class Noise {
constructor(octaves = 1) {
this.p = new Uint8Array(512);
this.octaves = octaves;
for (let i = 0; i < 512; ++i) this.p[i] = Math.random() * 256 * 100;
}
_lerp(t, a, b) { return a + t * (b - a); }
_fade(t) { return t * t * (3 - 2 * t); } // smooth-step
_grad(i, x, y) {
const v = (i & 1) === 0 ? x : y;
return (i & 2) === 0 ? -v : v;
}
noise2d(x, y) {
const X = Math.floor(x) & 255, Y = Math.floor(y) & 255;
const xf = x - Math.floor(x), yf = y - Math.floor(y);
const u = this._fade(xf), v = this._fade(yf);
const p0 = this.p[X] + Y, p1 = this.p[X + 1] + Y;
return this._lerp(v,
this._lerp(u, this._grad(this.p[p0], xf, yf ),
this._grad(this.p[p1], xf - 1, yf )),
this._lerp(u, this._grad(this.p[p0 + 1], xf, yf - 1),
this._grad(this.p[p1 + 1], xf - 1, yf - 1)));
}
// Multi-octave noise, normalised to [0, 1]
sample(x, y, persistence = 0.5) {
let amp = 1, freq = 1, total = 0, norm = 0;
for (let o = 0; o < this.octaves; o++) {
total += amp * (1 + this.noise2d(freq * x, freq * y)) / 2;
norm += amp;
amp *= persistence;
freq *= 2;
}
return total / norm; // → [0, 1]
}
}
const perlin = new Noise(3);
function envelope(t) {
// Squared-sine bell centred at clusterSide, width ~0.6 of canvas
const width = 0.62;
const raw = Math.cos(Math.PI * (t - clusterSide) / width);
return Math.max(0, raw) * Math.max(0, raw); // cos² → smooth, non-negative
}
function walk(i) {
// Map line index to y-position: bottom → top (-88 … +88)
const yBase = -88 + (i / Math.max(numLines - 1, 1)) * 176;
turtle.penup();
let penDown = false;
for (let xi = -100; xi <= 100; xi++) {
const t = (xi + 100) / 200; // normalised x ∈ [0,1]
const env = envelope(t); // amplitude envelope
// Two octaves of noise: coarse shape + fine detail
const n = perlin.sample(xi * noiseFreqX, i * noiseFreqY, 0.55);
const y = yBase + env * maxAmplitude * (n - 0.5) * 2; // centred offset
turtle.goto(xi, y);
if (!penDown) { turtle.pendown(); penDown = true; }
}
turtle.penup();
return i < numLines - 1;
}