Source code of plot #055 back to plot

Download full working sketch as 055.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} from "./utils/boilerplate.js";
import {mulberry32, setRandomGenerator, rand, rand_range} from "./utils/random.js"
import {kdTree} from "./utils/kdTree.js";

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

const rf = 1;
const segLen = 2;
let seed = Math.round(Math.random() * 65535);
// seed = 57788;

setSketch(async function () {
  setRandomGenerator(mulberry32(seed));
  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.strokeWidth = 2;
  paper.project.currentStyle.strokeColor = "black";

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

  const nCurves = 150;
  const xfact = 0.475
  const yfact = 0.475;
  for (let i = 0; i < nCurves; ++i) {
    const prms = [];
    const nHarm = Math.floor(7 * rand()) + 1;
    while (prms.length < nHarm) {
      const prm = {
        h: (prms.length + 2) * 0.5,
        a: (rand() - 0.5) ** 3,
      };
      prms.push(prm);
    }
    const paths = makeCurvePaths(prms);
    const canvasPaths = [];
    for (const pts of paths) {
      let canvasPts = pts.map(p => new Point(w * (0.5 + p.x * xfact), h * (0.5 + p.y * yfact)));
      canvasPts = sparser(canvasPts, segLen);
      if ((canvasPaths.length%2) == 0) canvasPts.reverse();
      canvasPaths.push(sparser(canvasPts, segLen));
    }
    for (const pts of canvasPaths) {
      const visiblePaths = getVisiblePaths(pts, kdt, 3);
      for (const pathPts of visiblePaths) {
        const shortenedPts = shorten(pathPts, 5);
        if (shortenedPts.length < 5) continue;
        const path = new paper.Path(shortenedPts);
        paper.project.activeLayer.addChild(path);
      }
    }
    for (const pts of canvasPaths) {
      for (const pt of pts) kdt.insert(pt);
    }
    await spin();
  }
}

function makeCurvePaths(prms) {
  const eps = 1e-6;
  const nSegs = 1e4;
  let pts = [];
  let max = Number.MIN_VALUE;
  for (let i = 0; i <= nSegs; ++i) {
    const x = 2 * i / nSegs - 1;
    let y = 0;
    for (const prm of prms) {
      let phase = 0;
      if (Math.abs((prm.h%1) - 0.5) < eps)
        phase = Math.PI * 0.5;
      y += prm.a * Math.sin(prm.h * x * Math.PI + phase);
    }
    pts.push(new Point(x, y));
    if (Math.abs(y) > max) max = Math.abs(y);
  }
  for (const pt of pts) {
    pt.y /= max;
    pt.y *= Math.exp(-4*(pt.x)**2);
  }
  return [pts];
}

function sparser(pts, minLen) {
  const res = [pts[0]];
  let travel = 0;
  for (let i = 1; i < pts.length; ++i) {
    travel += pts[i].subtract(pts[i-1]).length;
    if (travel >= minLen || i == pts.length - 1) {
      res.push(pts[i]);
      travel = 0;
    }
  }
  return res;
}

function shorten(pts, byLen) {
  let firstIx = -1, lastIx = -1;
  for (let travel = 0, i = 1; travel < byLen && i < pts.length; ++i) {
    travel += pts[i].subtract(pts[i-1]).length;
    if (travel >= byLen) firstIx = i;
  }
  for (let travel = 0, i = pts.length - 2; travel < byLen && i >= 0; --i) {
    travel += pts[i].subtract(pts[i+1]).length;
    if (travel >= byLen) lastIx = i;
  }
  if (firstIx == -1 || lastIx == -1 || firstIx >= lastIx)
    return [];
  return pts.slice(firstIx, lastIx + 1);
}

function getVisiblePaths(pts, kdt, dist) {

  // Result: array of array of points.
  const polys = [];
  let currPts = [];

  for (const pt of pts) {
    const neighbors = kdt.nearest(pt, 1, dist);
    const visible = neighbors.length == 0;
    if (!visible) addCurrentPoints();
    else currPts.push(pt);
  }
  addCurrentPoints();
  return polys;

  function addCurrentPoints() {
    if (currPts.length == 0) return;
    if (currPts.length >= 2) {
      polys.push(currPts);
    }
    currPts = [];
  }

}