add new path generator. let it bake for a while before removing the old one

This commit is contained in:
Stewart Allen 2022-10-04 00:25:44 -04:00
commit 1d47ec9473
4 changed files with 328 additions and 25 deletions

View file

@ -31,8 +31,8 @@ AP.equals = function(arr) {
return true;
};
AP.peek = function() {
return this[this.length - 1];
AP.peek = function(val = 1) {
return this[this.length - val];
};
AP.drop = function(pre = 0, post = 0) {

View file

@ -105,9 +105,249 @@ function poly2polyEmit(array, startPoint, emitter, opt = {}) {
return startPoint;
}
function calc_normal(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
let len = Math.sqrt(dx * dx + dy * dy);
let mn = (1 / len);
dx *= mn;
dy *= mn;
return({ dx: dy, dy: -dx, p1, p2, len });
}
function end_vertex(n1, n2, off, start) {
let dx, dy;
if (start) {
dx = n2.dx * off;
dy = n2.dy * off;
} else {
dx = n1.dx * off;
dy = n1.dy * off;
}
return { dx, dy, vp: n1.p2 };
}
function calc_vertex(n1, n2, off, vp) {
let dx, dy, io, vl, q, r;
r = 1 + (n1.dx * n2.dx + n1.dy * n2.dy);
q = off / r;
// handle spurs that switch back 180 degrees
if (q === Infinity) {
q = 0;
}
dx = (n1.dx + n2.dx) * q;
dy = (n1.dy + n2.dy) * q;
// io tells us whether we're turning left or right
io = (n1.dx * n2.dy - n2.dx * n1.dy);
// vertex length can be compared to the previons and next
// segment lengths to see if we're highly acute
vl = Math.sqrt(dx * dx + dy * dy);
return { dx, dy, vp: vp || n1.p2, io, vl };
}
function v2pl(rec) {
let p = rec.vp.clone();
p.x += rec.dx;
p.y += rec.dy;
p.vp = rec.vp;
return p;
}
function v2pr(rec) {
let p = rec.vp.clone();
p.x -= rec.dx;
p.y -= rec.dy;
p.vp = rec.vp;
return p;
}
function pointsToPath(points, offset, open, miter = 1.5) {
const absoff = Math.abs(offset);
// calculate segment normals which are used to calculate vertex normals
// next segment info is associated with the current point
const nupoints = [];
for (let i=0, l=points.length; i<l; i++) {
let p1 = points[i];
let p2 = points[(i+1)%l];
p1.normal = calc_normal(p1, p2);
// drop next duplicate point if segment length is 0
// is possible there are 3 dups in a row and this is
// not handled. could make if a while, but that could
// end up in a loop without additional checks
if (p1.normal.len === 0) {
p1.normal = calc_normal(p1, points[(i+2)%l]);
i++;
}
nupoints.push(p1);
}
// when points are dropped, we need the new array
points = nupoints;
// generate left / right paths and triangle faces
const left = [];
const right = [];
const faces = [];
// calculate vertex normals from segments normals
// vertex info is associated with the origin point
let fl, fr;
for (let i=0, l=points.length; i<l; i++) {
let n1 = points[(i+l-1)%l].normal;
let n2 = points[(i+l)%l].normal;
let vn = open && (i === 0 || i === l-1) ?
end_vertex(n1, n2, offset, i === 0) :
calc_vertex(n1, n2, offset);
let { p1, p2 } = n2;
let { io, vl } = vn;
if (offset < 0) {
io = -io;
}
let split_left = false, split_right = false;
if (io > 0) { // right
split_left = vl > absoff * miter;
split_right = vl > Math.min(n1.len, n2.len) + absoff;
} else { // left
split_right = vl > absoff * miter;
split_left = vl > Math.min(n1.len, n2.len) + absoff;
}
let l0 = left.peek(1);
let r0 = right.peek(1);
if (split_left || split_right) {
// shorten each leg and insert new point
let delta = 0.1;
let np1 = p1.clone().move({ x: n1.dy * delta, y: -n1.dx * delta, z: 0 });
let np2 = p1.clone().move({ x:-n2.dy * delta, y: n2.dx * delta, z: 0 });
let sn1 = np1.normal = calc_normal(np1, np2);
let sn2 = np2.normal = p1.normal;
let nv1 = calc_vertex(n1.p1.normal, sn1, offset, np1);
let nv2 = calc_vertex(sn1, sn2, offset, np2);
if (split_right) {
right.push(v2pr(nv1), v2pr(nv2));
} else {
right.push(v2pr(vn));
}
if (split_left) {
left.push(v2pl(nv1), v2pl(nv2));
} else {
left.push(v2pl(vn));
}
if (faces) {
let l1 = left.peek(1);
let r1 = right.peek(1);
let l2 = left.peek(2);
let r2 = right.peek(2);
if (split_left && split_right) {
faces.push(l1, l2, r1);
faces.push(r2, r1, l2);
fl = fl || l1;
fr = fr || r2;
} else if (split_left) {
faces.push(l1, l2, r1);
fl = fl || l2;
fr = fr || r1;
} else { // split right
faces.push(r2, r1, l1);
fl = fl || l1;
fr = fr || r2;
}
}
} else {
left.push(v2pl(vn));
right.push(v2pr(vn));
fl = fl || left.peek(1);
fr = fr || right.peek(1);
}
if (faces && l0 && r0) {
let l1 = left.peek(split_left ? 2 : 1);
let r1 = right.peek(split_right ? 2 : 1);
faces.push(l1, l0, r1);
faces.push(r0, r1, l0);
}
}
if (open) {
// move open ends by offset length
const p0 = points[0].normal;
const l0 = left[0];
const r0 = right[0];
l0.x += p0.dy * offset;
l0.y -= p0.dx * offset;
r0.x += p0.dy * offset;
r0.y -= p0.dx * offset;
const pn = points.peek(2).normal;
const ln = left.peek();
const rn = right.peek();
ln.x -= pn.dy * offset;
ln.y += pn.dx * offset;
rn.x -= pn.dy * offset;
rn.y += pn.dx * offset;
}
if (!open && faces) {
let l1 = left.peek(1);
let r1 = right.peek(1);
faces.push(fl, l1, fr);
faces.push(r1, fr, l1);
}
return { left, right, faces, open };
}
function pathTo3D(path, height, z) {
const { faces, left, right, open } = path;
const out = [];
if (z !== undefined) {
for (let p of faces) {
p.z = z;
}
}
for (let p of faces) {
out.push(p.x, p.y, p.z - height);
}
for (let p of faces.slice().reverse()) {
out.push(p.x, p.y, p.z + height);
}
for (let i=0, l=left.length, tl = open ? l-1 : l; i<tl; i++) {
let p0 = left[i];
let p1 = left[(i+1)%l];
out.push(p0.x, p0.y, p0.z + height);
out.push(p0.x, p0.y, p0.z - height);
out.push(p1.x, p1.y, p1.z - height);
out.push(p1.x, p1.y, p1.z - height);
out.push(p1.x, p1.y, p1.z + height);
out.push(p0.x, p0.y, p0.z + height);
}
for (let i=0, l=right.length, tl = open ? l-1 : l; i<tl; i++) {
let p0 = right[i];
let p1 = right[(i+1)%l];
out.push(p0.x, p0.y, p0.z + height);
out.push(p1.x, p1.y, p1.z - height);
out.push(p0.x, p0.y, p0.z - height);
out.push(p1.x, p1.y, p1.z - height);
out.push(p0.x, p0.y, p0.z + height);
out.push(p1.x, p1.y, p1.z + height);
}
if (open) {
let l0 = left[0];
let r0 = right[0];
out.push(l0.x, l0.y, l0.z + height);
out.push(r0.x, r0.y, r0.z - height);
out.push(l0.x, l0.y, l0.z - height);
out.push(r0.x, r0.y, r0.z + height);
out.push(r0.x, r0.y, r0.z - height);
out.push(l0.x, l0.y, l0.z + height);
let ln = left.peek();
let rn = right.peek();
out.push(ln.x, ln.y, ln.z + height);
out.push(ln.x, ln.y, ln.z - height);
out.push(rn.x, rn.y, rn.z - height);
out.push(rn.x, rn.y, rn.z + height);
out.push(ln.x, ln.y, ln.z + height);
out.push(rn.x, rn.y, rn.z - height);
}
return out;
}
base.paths = {
poly2polyEmit,
tip2tipEmit
tip2tipEmit,
pointsToPath,
pathTo3D
};
});

View file

@ -3,6 +3,7 @@
"use strict";
// dep: geo.base
// dep: geo.paths
// dep: ext.clip2
// dep: ext.earcut
// dep: geo.point
@ -59,6 +60,14 @@ class Polygon {
return bounds;
}
toPath2D(offset) {
return base.paths.pointsToPath(this.points, offset, this.open);
}
toPath3D(offset, height, z) {
return base.paths.pathTo3D(this.toPath2D(offset), height, z);
}
toString(verbose) {
let l;
if (this.inner && this.inner.length) {

View file

@ -11,6 +11,7 @@ const { base, kiri } = root;
const { polygons, newPolygon } = base;
const POLY = base.polygons;
const newPath = true;
class Layers {
constructor() {
@ -143,30 +144,41 @@ class Layers {
const z = polys[0].getZ(), faces = this.current.faces;
const off_opt = { z, flat: true };
polys.forEach(poly => {
let exp = off_opt.outs = [];
if (poly.isOpen()) {
// shorten line by offset
const pp = poly.points, pl = pp.length;
pp[0] = pp[0].offsetPointTo(pp[1], offset);
pp[pl-1] = pp[pl-1].offsetPointTo(pp[pl-2], offset);
exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z));
// force re-nest if offsets cause ends to join
exp = POLY.flatten(exp,[],true);
this.stats.flat_line = 0;
} else if (offset) {
POLY.offset([poly], offset * 0.9, off_opt);
POLY.offset([poly], -offset * 0.9, off_opt);
this.stats.flat_poly = 0;
}
if (opts.outline) {
this.addPolys(exp.clone());
}
const faceidx = faces.length / 3;
POLY.nest(exp).forEach((poly,i) => {
poly.earcut().forEach(ep => {
ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) });
if (newPath) {
const path = poly.toPath2D(offset * 0.95);
for (let p of path.faces) {
faces.push(p.x, p.y, p.z);
}
if (opts.outline) {
this.addPolys(newPolygon().addPoints(path.left).setOpen());
this.addPolys(newPolygon().addPoints(path.right).setOpen());
}
} else {
let exp = off_opt.outs = [];
if (poly.isOpen()) {
// shorten line by offset
const pp = poly.points, pl = pp.length;
pp[0] = pp[0].offsetPointTo(pp[1], offset);
pp[pl-1] = pp[pl-1].offsetPointTo(pp[pl-2], offset);
exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z));
// force re-nest if offsets cause ends to join
exp = POLY.flatten(exp,[],true);
this.stats.flat_line = 0;
} else if (offset) {
POLY.offset([poly], offset * 0.9, off_opt);
POLY.offset([poly], -offset * 0.9, off_opt);
this.stats.flat_poly = 0;
}
if (opts.outline) {
this.addPolys(exp.clone());
}
POLY.nest(exp).forEach((poly,i) => {
poly.earcut().forEach(ep => {
ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) });
});
});
});
}
const cur = this.current;
const color = opts.color ?
(typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) :
@ -189,11 +201,53 @@ class Layers {
const opts = options || {};
const height = opts.height || 1;
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
if (newPath) {
for (let poly of polys) {
if (poly.length < (poly.open ? 2 : 3)) {
continue;
}
const z = opts.z || poly.getZ();
const faces = poly.toPath3D(offset * 1, height, 0);
const cur = this.current;
const one = cur.paths[0];
if (one) {
// merge all contour geometry for massive speed gain
// todo: recode to concat all geometries at once, and not incrementally
const add = one.faces.length / 3;
const feces = new Float32Array(one.faces.length + faces.length);
const indln = one.faces.length / 3;
feces.set(one.faces);
feces.set(faces, one.faces.length);
one.faces = feces;
// allow changing colors
if (opts.color) {
if (!cur.cpath) {
cur.cpath = [ Object.assign({ start: 0, count: indln - 1 }, cur.color) ];
}
// rewrite last color count if color or opacity have changed
const pc = cur.cpath[cur.cpath.length - 1];
if (pc.face !== opts.color.face || pc.opacity !== opts.color.opacity) {
pc.count = indln;
cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color));
}
}
} else {
cur.paths.push({ index: [], faces, z });
if (opts.color) {
cur.cpath = [ Object.assign({ start: 0, count: Infinity }, opts.color) ];
}
}
this.stats.contour++;
}
return this;
}
const profiles = this.profiles;
const prokey = `${offset}x${height}`;
if (!profiles[prokey]) {