Source code of plot #057 back to plot

Download full working sketch as 057.tar.gz.
Unzip, then start a local web server and load the page in a browser.

///<reference path="../pub/lib/paper.d.ts" />
import {info, init, loadLib, setSketch, spin, dbgRedraw} from "./utils/boilerplate.js";
import {mulberry32, setRandomGenerator, rand} from "./utils/random.js"
import {SimplexNoise} from "./utils/simplex-noise.js";
import {kdTree} from "./utils/kdTree.js";

const pw = 2100;    // Paper width
const ph = 1480;    // Paper height
const w = 2100;     // Drawing width
const h = 1480;     // Drawing height

let simplex1, simplex2;
let seed = Math.round(Math.random() * 65535);

setSketch(async function () {
  setRandomGenerator(mulberry32(seed));
  simplex1 = new SimplexNoise(rand() * 100);
  simplex2 = new SimplexNoise(rand() * 100);
  info("Seed: " + seed);
  init(w, h, pw, ph);

  const startTime = performance.now();
  await draw();
  const elapsed = performance.now() - startTime;
  console.log(`Drawn in ${elapsed} msec`);
});

async function draw() {
  paper.project.currentStyle.strokeColor = "black";
  paper.project.currentStyle.strokeWidth = 2;

  const kdt1 = new kdTree([], (a, b) => Math.sqrt((a.x-b.x)**2 + (a.y-b.y)**2), ["x", "y"]);
  const kdt2 = new kdTree([], (a, b) => Math.sqrt((a.x-b.x)**2 + (a.y-b.y)**2), ["x", "y"]);

  const originW = 1480;
  const originH = 1050;
  const curves = [];
  const nCurves = 1200;
  const startMinGap = 10.1;
  const endMinGap = 5;
  const minLen = 20;
  const maxLen = 200;

  while (curves.length < nCurves) {
    let startPt = getRandomPoint(originW, originH);
    while (true) {
      if (kdt1.nearest(startPt, 1, startMinGap) + kdt2.nearest(startPt, 1, startMinGap) == 0)
        break;
      startPt = getRandomPoint(originW, originH);
    }
    const curveSet = rand() < 0.5 ? 0 : 1;
    const kdt = curveSet == 0 ? kdt1 : kdt2;
    let [pts, len] = genCurve(startPt, curveSet, kdt1, kdt2, endMinGap, maxLen);
    if (len < minLen) continue;

    const path = addPath(pts);
    curves.push({pts, path});

    for (const pt of pts)
      kdt.insert(pt);

    if ((curves.length%10) == 0) await spin();
  }

  await spin();

  const nCurvesPruned = Math.round(nCurves * 0.6);
  while (curves.length > nCurvesPruned) {
    const ix = Math.floor(curves.length * rand());
    const c = curves[ix];
    const midPt = c.pts[Math.round(c.pts.length/2)];
    let dn = getNormDist(midPt);
    dn = Math.pow(dn, 4);
    const thresh = rand();
    if (thresh > dn)
      continue;
    curves.splice(ix, 1);
    c.path.remove();
  }

  dbgRedraw();
  for (const pts of curves) addPath(pts);
  await spin();
}

function getNormDist(pt) {
  const dist = Math.sqrt((pt.x-w/2)**2 + (pt.y-h/2)**2);
  return dist / w;
}

function addPath(pts) {
  const path = new Path({segments: pts});
  project.activeLayer.addChild(path);
  return path;
}

function getRandomPoint(originW, originH) {
  return new Point(
    Math.round(w * 0.5 + (rand() - 0.5) * originW),
    Math.round(h * 0.5 + (rand() - 0.5) * originH));
}

function genCurve(startPt, curveSet, kdt1, kdt2, endMinGap, maxLen) {

  const kdtMine = curveSet == 0 ? kdt1 : kdt2;
  const kdtOther = curveSet == 0 ? kdt2 : kdt1;
  const noise = curveSet == 0 ? simplex1 : simplex2;
  const stepLen = 2;

  const res = [startPt.clone()];
  let len = 0;
  for (let pt = next(res[0], 1); ; pt = next(pt, 1)) {
    if (kdtMine.nearest(pt, 1, endMinGap) != 0) break;
    if (kdtOther.nearest(pt, 1, stepLen) != 0) break;
    if (len > maxLen) break;
    res.push(pt);
    len += stepLen;
    if (res.length == 2) res[0].tan = pt.tan;
  }
  for (let pt = next(res[0], -1); ; pt = next(pt, -1)) {
    if (kdtMine.nearest(pt, 1, endMinGap) != 0) break;
    if (kdtOther.nearest(pt, 1, stepLen) != 0) break;
    if (len > maxLen) break;
    res.unshift(pt);
    len += stepLen;
  }
  return [res, len];

  function next(pt, dir) {
    const sampleX = ((pt.x / w) - 0.5) * 0.8 + 2;
    const sampleY = ((pt.y / h) - 0.5) * 0.8 + 2;
    let angle = Math.PI * noise.noise2D(sampleX, sampleY);
    if (dir < 0) angle += Math.PI;
    const res = new Point(
      pt.x + stepLen * Math.sin(angle),
      pt.y + stepLen * Math.cos(angle));
    res.tan = angle;
    return res;
  }
}