#container
View Compiled
document, body {
margin: 0;
min-height: 100vh;
}
body {
align-items: center;
display: flex;
justify-content: center;
}
#container {
align-items: center;
display: flex;
flex-direction: column;
}
button {
max-width: 200px;
margin-top: 10px;
}
View Compiled
import { SVG } from 'https://cdn.skypack.dev/@svgdotjs/svg.js'
import { Vec2 } from 'https://cdn.skypack.dev/wtc-math';
import * as dat from 'https://cdn.skypack.dev/dat.gui';
import fitCurve from 'https://cdn.skypack.dev/fit-curves';
console.clear();
const dimensions = new Vec2(750, 500);
const config = {
seed: 1565,
nscale: .002,
sscale: 10,
stepSize: 10,
steps: 1000,
r: 10,
iterations: 64,
pos: dimensions.multiplyNew(new Vec2(.2, .5)),
startDistance: 80,
breakLength: 1000,
colour: '#FF0066',
differenceThreshold: .8,
interationDistance: 1,
movementStrength: .05
};
const vars = {
i: 0
};
let noise;
var drawing = SVG().addTo('#container').size(dimensions.x, dimensions.y);
/// Create the download button
const dl = document.createElement('button');
dl.innerText = 'download';
dl.addEventListener('click', () => {
const fileName = "untitled.svg"
const url = "data:image/svg+xml;utf8," + encodeURIComponent(drawing.svg());
const link = document.createElement("a");
link.download = fileName;
link.href = url;
link.click();
});
document.body.querySelector('#container').appendChild(dl);
const setup = (pos) => {
if(pos instanceof Vec2) config.pos = pos;
drawing.clear();
noise = new SimplexNoise(config.seed);
vars.i = 0;
config.iterant = (Math.PI*2) / config.iterations;
draw();
};
const gui = new dat.GUI();
gui.add(config, 'seed').onChange(setup);
gui.add(config, 'sscale', -20, 20, .0001).onChange(setup);
gui.add(config, 'nscale', .0001, .01, .0001).onChange(setup);
gui.add(config, 'stepSize', 1, 30, 1).onChange(setup);
gui.add(config, 'steps', 20, 1000, 1).onChange(setup);
gui.add(config, 'iterations', 6, 512, 1).onChange(setup);
gui.add(config, 'startDistance', 0, 500, 1).onChange(setup);
gui.add(config, 'breakLength', 0, 1000, 1).onChange(setup);
gui.add(config, 'differenceThreshold', .1, 3., .05).onChange(setup);
gui.add(config, 'interationDistance', 1, 20, 1).onChange(setup);
gui.add(config, 'movementStrength', -.1, .1, .001).onChange(setup);
gui.close();
const lerp = (x,y,a)=>{
return x*(1-a)+y*a;
};
class Straightwalker {
#start
#position
#direction
#offset
#stepsize = 1
#i = 0
#points = []
#parent
constructor(pos, dir, offset, parent) {
if(pos instanceof Vec2) this.#start = pos;
this.offset = offset;
this.pos = this.start.clone();
this.dir = dir;
this.#points.push(this.pos.clone());
this.#parent = parent;
}
walk(l) {
this.pos.add(this.dir.scaleNew(l));
this.#points.push(this.pos.clone());
}
get length() {
return this.start.subtractNew(this.pos).length;
}
set index(i) {
if(!isNaN(i)) this.#i = Math.round(i);
}
get index() {
return this.#i;
}
set offset(p) {
if(p instanceof Vec2) this.#offset = p;
}
get offset() {
return this.#offset || new Vec2(0,0);
}
set pos(p) {
if(p instanceof Vec2) this.#position = p;
}
get pos() {
return this.#position || this.start.clone();
}
get start() {
return this.#start || new Vec2(0, 0);
}
set dir(p) {
if(p instanceof Vec2) this.#direction = p.normalise();
}
get dir() {
return this.#direction || new Vec2(1, 0);
}
get points() {
let points = this.#points;
if(this.index == 0) {
points = points.slice(Math.floor(config.startDistance / config.stepSize));
}
return points.map(p => p.array);
}
get bezier() {
const points = this.points;
if(points.length <= 1) return;
const bezierCurves = fitCurve(this.points, .1);
let str = "";
bezierCurves.map(function(bezier, i) {
if (i == 0) {
str += "M " + bezier[0][0] + " " + bezier[0][1];
}
str += "C " + bezier[1][0] + " " + bezier[1][1] + ", " +
bezier[2][0] + " " + bezier[2][1] + ", " +
bezier[3][0] + " " + bezier[3][1] + " ";
});
return str;
}
get parent() {
return this.#parent || this;
}
}
let i = 0;
const dir = new Vec2(1,0);
const drawStep = (delta) => {
const ws = [];
const rd = dir.rotateNew(vars.i);
const w = new Straightwalker(config.pos, rd, new Vec2(Math.cos(rd.angle) * config.startDistance, Math.sin(rd.angle) * config.startDistance));
w.n = noise.noise2D(w.pos.scaleNew(config.nscale).array)*config.sscale;
w.o = true;
ws.push(w);
let l = 0;
for(let i = 0; i < config.steps; i++) {
if(ws[0].length > config.breakLength) break;
if(
ws[0].pos.x < -20 ||
ws[0].pos.x > dimensions.width + 20 ||
ws[0].pos.y < -20 ||
ws[0].pos.y > dimensions.height + 20
) break;
ws.forEach((w, i) => {
w.walk(config.stepSize);
const oldN = w.n;
const parent = w.parent;
const p = w.pos.clone();
let n = noise.noise2D(p.scaleNew(config.nscale).array);
// w.dir.angle += n*config.movementStrength;
if(w.index === 0) w.dir.angle += n*config.movementStrength;
// if(w.index===0) w.dir.angle = lerp(w.dir.angle, n*(Math.PI*2) , .0003);
else w.dir = parent.dir.clone();
n *= config.sscale;
const rd = w.dir.clone();
{
if(oldN - n > config.differenceThreshold && i == l && w.index >=0) {
w.n = n;
const nw = new Straightwalker(p.add(rd.rotateNew(Math.PI*.5).scale(config.interationDistance)), rd, null, parent);
nw.index = w.index+1;
nw.n = n;
ws.push(nw);
l++;
} else if(oldN - n < -config.differenceThreshold && w.index >=0) {
w.n = n;
if(!w.o) {
drawing.path(w.bezier).fill('none').stroke({ width: .5, color:config.colour });
ws.splice(i, 1);
l--;
}
}
if(oldN - n < -config.differenceThreshold && i == l && w.index <=0) {
w.n = n;
const nw = new Straightwalker(p.add(rd.rotateNew(Math.PI*-.5).scale(config.interationDistance)), rd, null, parent);
nw.index = w.index-1;
nw.n = n;
ws.push(nw);
l++;
} else if(oldN - n > config.differenceThreshold && w.index <=0) {
w.n = n;
if(!w.o) {
// drawing.line([w.start.array, w.pos.array]).fill("none").stroke(config.colour);
drawing.path(w.bezier).fill('none').stroke({ width: .5, color:config.colour });
// drawing.polyline(w.points).fill('none').stroke({ width: .5, color:config.colour });
ws.splice(i, 1);
l--;
}
}
}
});
}
ws.forEach((w) => {
// drawing.line([w.start.addNew(w.offset).array, w.pos.array]).fill("none").stroke(config.colour);
drawing.path(w.bezier).fill('none').stroke({ width: .5, color:config.colour });
});
vars.i += config.iterant;
if(vars.i < Math.PI * 2 - config.iterant*.5) {
requestAnimationFrame(drawStep);
}
}
let interval;
const draw = () => {
drawing.rect(dimensions.width, dimensions.height).fill("none").stroke('#f06');
const dir = new Vec2(1,0);
const ws = [];
drawStep();
ws.forEach((w) => {
drawing.line([w.start.array, w.pos.array]).fill("none").stroke('#f06');
});
}
const floatRandomBetween = (min, max) => {
return Math.random() * (max - min) + min;
};
setup(dimensions.multiplyNew(new Vec2(.5, .5)));
const svg = document.body.querySelector('svg');
document.body.querySelector('svg').addEventListener('click', (e) => {
const os = svg.getBoundingClientRect();
setup(new Vec2(e.clientX - os.x, e.clientY - os.y));
});
////////////////////////////////////////////////////////////////
// Simplex Noise utility code. Created by Reinder Nijhoff 2020
// https://turtletoy.net/turtle/6e4e06d42e
// Based on: http://webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
////////////////////////////////////////////////////////////////
function SimplexNoise(seed = 1) {
const grad = [ [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],
[1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],
[0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1] ];
const perm = new Uint8Array(512);
const F2 = (Math.sqrt(3) - 1) / 2, F3 = 1/3;
const G2 = (3 - Math.sqrt(3)) / 6, G3 = 1/6;
const dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
const sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]];
const dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
const sub3 = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
class SimplexNoise {
constructor(seed = 1) {
for (let i = 0; i < 512; i++) {
perm[i] = i & 255;
}
for (let i = 0; i < 255; i++) {
const r = (seed = this.hash(i+seed)) % (256 - i) + i;
const swp = perm[i];
perm[i + 256] = perm[i] = perm[r];
perm[r + 256] = perm[r] = swp;
}
}
noise2D(p) {
const s = dot2(p, [F2, F2]);
const c = [Math.floor(p[0] + s), Math.floor(p[1] + s)];
const i = c[0] & 255, j = c[1] & 255;
const t = dot2(c, [G2, G2]);
const p0 = sub2(p, sub2(c, [t, t]));
const o = p0[0] > p0[1] ? [1, 0] : [0, 1];
const p1 = sub2(sub2(p0, o), [-G2, -G2]);
const p2 = sub2(p0, [1-2*G2, 1-2*G2]);
let n = Math.max(0, 0.5-dot2(p0, p0))**4 * dot2(grad[perm[i+perm[j]] % 12], p0);
n += Math.max(0, 0.5-dot2(p1, p1))**4 * dot2(grad[perm[i+o[0]+perm[j+o[1]]] % 12], p1);
n += Math.max(0, 0.5-dot2(p2, p2))**4 * dot2(grad[perm[i+1+perm[j+1]] % 12], p2);
return 70 * n;
}
hash(i) {
i = 1103515245 * ((i >> 1) ^ i);
const h32 = 1103515245 * (i ^ (i>>3));
return h32 ^ (h32 >> 16);
}
}
return new SimplexNoise(seed);
}
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.