#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 fitCurve from 'https://cdn.skypack.dev/fit-curves';
console.clear();

const config = {
  seed: 1337,
  nscale: .005,
  sscale: 20,
  stepSize: 1,
  r: 10,
  k: 512,
  stagePadding: 40
};
const vars = {
  newPositions: [],
  activeList: [],
  fullList: [],
  walkers: [],
  grid: null,
  newPosition: null
}
let noise;

const dimensions = new Vec2(750, 500);

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);

let running;
const setup = (x=dimensions.x/2, y=dimensions.y/2) => {
  running = false;
  setTimeout(() => {
    running = true;
    vars.currentPosition = [];
    vars.activeList = [];

    config.seed = floatRandomBetween(0, 99999);
    
    config.nscale = floatRandomBetween(.001, .01);
    config.sscale = floatRandomBetween(Math.PI, Math.PI*.25);

    noise = new SimplexNoise(config.seed);
    
    vars.stageHeight = dimensions.y + config.stagePadding * 2.;
    vars.stepsize = vars.stageHeight / config.numwalkers;
    vars.position = new Vec2(0,-config.stagePadding);
    
    config.r = 4;

    vars.grid = new Grid({ 
      size: dimensions.addScalarNew(config.r / Math.SQRT2).array,
      cellSize: config.r*1.3,
      fill: -1
    });
    
    const poses = [
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y),
      new Vec2(Math.random()*dimensions.x, Math.random()*dimensions.y)
    ];
    poses.forEach((p) => {
      const pos = new Vec2(p.x, p.y);
      const n = noise.noise2D(pos.clone().scale(config.nscale).array)*config.sscale;
      const body = new Body(pos, n, 0);
      vars.grid.addChildAtPosition(body, pos);
      vars.activeList.push(body);
      vars.newPositions.push(body);
    })
    

    drawing.clear();
    draw();
  }, 10);
};

class Grid {
  static #defaults = {
    size: dimensions.clone(),
    cellSize: new Vec2(50,50),
    fill: null
  };
  #grid = [];
  #size;
  #cellSize;
  
  constructor(settings) {
    settings = Object.assign({}, Grid.#defaults, settings);
    this.#size = Vec2.interpolate(settings.size) || Grid.#defaults.size;
    this.#cellSize = Vec2.interpolate(settings.cellSize) || Grid.#defaults.cellSize;
    this.#grid.length = this.gridSize.area;
    this.#grid.fill(settings.fill || Grid.#defaults.fill);
  }

  addChild(child, i) {
    this.#grid[i] = child;
  }
  addChildAtPosition(child, pos) {
    this.#grid[this.getArrayPosition(pos)] = child;
  }
  addChildAtGridPosition(child, gridPos) {
    this.#grid[this.getArrayPositionFromGridPos(gridPos)] = child;
  }

  getChild(i) {
    return this.#grid[i];
  }
  getChildAtPosition(pos) {
    return this.#grid[this.getArrayPosition(pos)];
  }
  getChildAtGridPosition(gridPos) {
    return this.#grid[this.getArrayPositionFromGridPos(gridPos)];
  }

  getGridPositionForIndex(i) {
    const gsize = this.gridSize;
    return new Vec2(i%gsize.x, Math.floor(i/gsize.x));
  }

  getArrayPositionFromGridPos(gpos) {
    const gsize = this.gridSize;
    if(
      gpos.x < 0 || gpos.x >= gsize.x ||
      gpos.y < 0 || gpos.y >= gsize.y )
      return null;
    
    gpos.x = gpos.x % gsize.x;
    const arraypos = (gpos.x) + (gpos.y*gsize.x);
    return arraypos;
  }

  getArrayPosition(realPos) {
    const gpos = this.getGridPos(realPos);
    return this.getArrayPositionFromGridPos(gpos);
  }

  getGridPos(realPos) {
    if(realPos instanceof Vec2) {
      return realPos.divideNew(this.#cellSize).floor();
    }
    // Throw an error
  }
  getSubPos(realPos) {
    if(realPos instanceof Vec2) {
      return realPos.modNew(this.#cellSize);
    }
    // Throw an error
  }
  getRealPos(gridPos) {
    if(gridPos instanceof Vec2) {
      return gridPos.multiplyNew(this.#cellSize);
    }
    // Throw an error
  }

  get size() {
    return this.#size;
  }
  get gridSize() {
    return this.#size.divideNew(this.#cellSize).floor();
  }
  get cellSize() {
    return this.#cellSize;
  }
  get length() {
    return this.#grid.length;
  }
}

class Body {
  constructor(pos, r, a) {
    this.pos = pos;
    this.r = r;
    this.a = a;
  }
}
let a=0;
const drawStep = () => {
  if(!running) return;
  if(vars.newPositions.length > 0) {
    // vars.newPositions.forEach(c => {
    //   vars.grid.addChildAtPosition(c, c.pos);
    // });
  }
  vars.newPositions = [];
  if(vars.activeList.length > 0) {
    vars.activeList.forEach((c, ri) => {
      if(Math.random() > .1 && ri > 1) return;
      let numfound = 0;
      for(var i = 0; i < config.k; i++) {
        // const a = floatRandomBetween(0, Math.PI*2);
        // a+=.01;
        // const l = floatRandomBetween(config.r, config.r*2);
        let a = c.a || 0;
        const n = noise.noise2D(c.pos.clone().scale(config.nscale).array)*config.sscale;
        a += n*.002;
        const l = floatRandomBetween(config.r, config.r*2);
        // const l = (Math.random()*.5*((n+1)*.6)+1)*config.r;
        const pos = new Vec2(Math.cos(a)*l, Math.sin(a)*l);
        // a = pos.angle;
        c.a = a;
        pos.add(c.pos);
        if(vars.grid.getChildAtPosition(pos) === -1) {
          const body = new Body(pos, n, n*Math.PI);
          // const body = new Body(pos, n, pos.subtractNew(c.pos).angle);
          vars.grid.addChildAtPosition(body, pos);
          vars.activeList.push(body);
          vars.newPositions.push(body);
          drawing.line([[c.pos.x, c.pos.y], [pos.x, pos.y]]).fill("none").stroke('#f06');
          numfound++;
          break;
        }
      }
      if(numfound === 0) {
        vars.activeList.splice(ri, 1);
      }
    });
    // const ri = Math.floor(0, floatRandomBetween(vars.activeList.length));
    // const c = vars.activeList[ri];
    requestAnimationFrame(drawStep);
  } else {
    console.log('end')
  }
}
const draw = () => {
  drawing.rect(dimensions.width, dimensions.height).fill("none").stroke('#f06');
  requestAnimationFrame(drawStep);
}


const floatRandomBetween = (min, max) => {
  return Math.random() * (max - min) + min;
};

setup();
const svg = document.body.querySelector('svg');
svg.addEventListener('click', (e) => {
  const os = svg.getBoundingClientRect();
  setup(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.