#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

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.