As I worked on these, I began to think of them as pinwheels. Getting the right “flutter” out was a challenge, and I also had a bug for a little where content ended up mostly off the page. I ended up trying to “weave” in the iteration by flipping whether I add or subtract it in the simplex noise for the x or y.
const Simplex = require("https://unpkg.com/simplex-noise@2.4.0/simplex-noise.js");
const simplex = new Simplex();
const width = 761;
const height = width;
// source: https://rosettacode.org/wiki/Map_range#ES6
const rangeMap = (a, b) => (s) => {
const [a1, a2] = a;
const [b1, b2] = b;
// Scaling up an order, and then down, to bypass a potential,
// precision issue with negative numbers.
return ((((b2 - b1) * (s - a1)) / (a2 - a1)) * 10 + 10 * b1) / 10;
}
function generatePaths() {
const start = [Math.random() * width, Math.random() * height];
const step = 200.0;
const path = [start];
for (let i = 0; i < 100; i++) {
const [px, py] = path.at(-1);
const theta = simplex.noise2D(px * px * 10 * +i * i, py * py * 10 - i * i);
const t = rangeMap([-1, 1], [-Math.PI, Math.PI])(theta);
const x =
Math.cos(t) * step + simplex.noise2D(px + i, py - i) * 100 + width / 2;
const y =
Math.sin(t) * step + simplex.noise2D(px - i, py + i) * 100 + height / 2;
path.push([x, y]);
}
return path;
}
function draw() {
const svg = d3.create("svg").attr("width", width).attr("height", height);
svg
.append("path")
.attr("d", d3.line()(generatePaths()))
.attr("fill", "none")
.attr("stroke", "black");
return svg.node();
}
