Source code of plot #030 back to plot

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


class Vec2 {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  clone() {
    return new Vec2(this.x, this.y);
  }
  equals(other) {
    return this.x == other.x && this.y == other.y;
  }
}

class Vec3 {
  constructor(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  rotx(angle) {
    const sin = Math.sin(angle);
    const cos = Math.cos(angle);
    return new Vec3(this.x, cos * this.y - sin * this.z, sin * this.y + cos * this.z);
  }
  roty(angle) {
    const sin = Math.sin(angle);
    const cos = Math.cos(angle);
    return new Vec3(this.x * cos + this.z * sin, this.y, this.x * (-sin) + this.z * cos);
  }
}


function makeHatch(x, y, angle, length, interval, count, startHalf) {
  let res = [];
  const sin = Math.sin(angle);
  const cos = Math.cos(angle);
  const rsin = Math.sin(angle - Math.PI / 2);
  const rcos = Math.cos(angle - Math.PI / 2);
  for (let i = 0; i < count; ++i) {
    const midOfs = startHalf ? interval * (i + 0.5) : interval * i;
    const midX = x + midOfs * rcos;
    const midY = y - midOfs * rsin;
    const x1 = midX + length / 2 * cos;
    const y1 = midY - length / 2 * sin;
    const x2 = midX - length / 2 * cos;
    const y2 = midY + length / 2 * sin;
    res.push([new Vec2(x1, y1), new Vec2(x2, y2)]);
  }
  return res;
}

function makePaperHatchLines(angle, length, interval, count, startHalf, midPt) {

  let ox = 0, oy = 0;
  if (midPt) { ox = midPt.x; oy = midPt.y; }

  let res = [];
  const sin = Math.sin(angle / 180 * Math.PI);
  const cos = Math.cos(angle / 180 * Math.PI);
  const rsin = Math.sin(angle / 180 * Math.PI - Math.PI / 2);
  const rcos = Math.cos(angle / 180 * Math.PI - Math.PI / 2);
  for (let i = -count; i < count; ++i) {
    const midOfs = startHalf ? interval * (i + 0.5) : interval * i;
    const midX = ox + midOfs * rcos;
    const midY = oy - midOfs * rsin;
    const x1 = midX + length / 2 * cos;
    const y1 = midY - length / 2 * sin;
    const x2 = midX - length / 2 * cos;
    const y2 = midY + length / 2 * sin;
    const line = paper.Path.Line(new paper.Point(x1, y1), new paper.Point(x2, y2));
    res.push(line);
    // DBG
    //line.strokeColor = "red";
    //paper.project.activeLayer.addChild(line);
    //paper.view.draw();
  }
  return res;
}

function shortenLine(line, val) {
  const a = line[0], b = line[1];
  const mid = new Vec2((a.x + b.x) / 2, (a.y + b.y) / 2);
  const len = Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
  if (len - val < 2) return null;
  const prop = (len - val) / len;
  const newA = new Vec2(mid.x + (a.x - mid.x) * prop, mid.y + (a.y - mid.y) * prop);
  const newB = new Vec2(mid.x + (b.x - mid.x) * prop, mid.y + (b.y - mid.y) * prop);
  return [newA, newB];
}

function shortenPaperLine(line, val) {
  if (val == 0) return line;
  const a = line.segments[0].point, b = line.segments[1].point;
  const mid = new Vec2((a.x + b.x) / 2, (a.y + b.y) / 2);
  const len = Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
  if (len - val < 2) return null;
  const prop = (len - val) / len;
  const newA = new Point(mid.x + (a.x - mid.x) * prop, mid.y + (a.y - mid.y) * prop);
  const newB = new Point(mid.x + (b.x - mid.x) * prop, mid.y + (b.y - mid.y) * prop);
  return Path.Line(newA, newB);
}

function lerpPt(pta, ptb, f) {
  return new paper.Point(pta.x + (ptb.x - pta.x) * f, pta.y + (ptb.y - pta.y) * f);
}

function lnPtDist(ln1, ln2, pt) {
  let B = pt.y - ln1.y;
  let A = pt.x - ln1.x;
  let C = ln2.x - ln1.x;
  let D = ln2.y - ln1.y;

  let dot = A * C + B * D;
  let len_sq = C * C + D * D;
  let param = -1;
  if (len_sq != 0) //in case of 0 length line
    param = dot / len_sq;

  let xx, yy;

  if (param < 0) {
    xx = ln1.x;
    yy = ln1.y;
  } else if (param > 1) {
    xx = ln2.x;
    yy = ln2.y;
  } else {
    xx = ln1.x + param * C;
    yy = ln1.y + param * D;
  }

  let dx = pt.x - xx;
  let dy = pt.y - yy;
  return Math.sqrt(dx * dx + dy * dy);
}

function getMaskedPoly(pts, negMasks, posMasks) {

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

  // Bounding rectangle: for quick test before invoking expensive "contains"
  const negRects = [];
  negMasks.forEach(x => negRects.push(x.bounds));
  const posRects = [];
  posMasks.forEach(x => posRects.push(x.bounds));
  let currPts = [];

  for (const pt of pts) {
    let visible = true;
    for (let i = 0; i < posRects.length && visible; ++i)
      visible &= posRects[i].contains(pt);
    for (let i = 0; i < negRects.length && visible; ++i) {
      if (negRects[i].contains(pt) && negMasks[i].contains(pt))
        visible = false;
    }
    for (let i = 0; i < posMasks.length && visible; ++i)
      visible &= posMasks[i].contains(pt);
    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 = [];
  }
}

function getMaskedLine(pt1, pt2, negMasks, posMasks, segLength = 2) {

  // Build points: short segments of the requested length
  const lineVect = pt2.subtract(pt1);
  const lineLength = lineVect.length;
  const nSegs = Math.max(2, Math.round(lineLength / segLength));
  const segVect = lineVect.divide(nSegs);
  const pts = [];
  for (let i = 0; i <= nSegs; ++i) {
    pts.push(pt1.add(segVect.multiply(i)));
  }

  // Get polylines
  const polys = getMaskedPoly(pts, negMasks, posMasks);
  const res = [];

  // Simplify: just keep first and last point of each polyline.
  // These are all straight lines.
  polys.forEach(poly => {
    const pta = poly[0];
    const ptb = poly[poly.length - 1];
    res.push([pta, ptb]);
  });

  return res;
}


export {
  Vec2, Vec3, shortenLine, shortenPaperLine, lerpPt, lnPtDist,
  makeHatch, makePaperHatchLines,
  getMaskedLine, getMaskedPoly
};