extract and somewhat generalize trace_noodle
This commit is contained in:
parent
2ca01ed004
commit
c8fe7fb660
1 changed files with 75 additions and 188 deletions
|
|
@ -167,69 +167,78 @@ function thin_type_3(params) {
|
||||||
let { z, top, count, offsetN, last, gaps } = params;
|
let { z, top, count, offsetN, last, gaps } = params;
|
||||||
|
|
||||||
// produce trace from outside of poly inward no more than max inset
|
// produce trace from outside of poly inward no more than max inset
|
||||||
let { noodle, remain } = top.poly.noodle(offsetN * count);
|
let noodleWidth = offsetN * count;
|
||||||
|
let { noodle, remain } = top.poly.noodle(noodleWidth);
|
||||||
|
|
||||||
top.gaps = remain;
|
top.gaps = remain;
|
||||||
|
|
||||||
let intersectChains = true;
|
// render inset "noodle"
|
||||||
let maxInsetBypass = false;
|
// shells.appendAll(noodle);
|
||||||
let altNuSegs = true;
|
|
||||||
|
|
||||||
let showNoodle = false;
|
|
||||||
let showMedLine = false;
|
|
||||||
let showInset = false;
|
|
||||||
let showExtrusion = true;
|
|
||||||
let showExtrudeInset = false;
|
|
||||||
let showChainRawPoints = false;
|
|
||||||
let showChainIntersect = false;
|
|
||||||
let showNuMedLine = false;
|
|
||||||
|
|
||||||
let thin = top.thin_fill = [];
|
let thin = top.thin_fill = [];
|
||||||
let shells = top.shells;
|
let shells = top.shells;
|
||||||
let sparse = top.fill_sparse = [];
|
let sparse = top.fill_sparse = [];
|
||||||
let scale = 1000;
|
|
||||||
let midR = offsetN * 0.5; // nominal extrusion width
|
let midR = offsetN * 0.5; // nominal extrusion width
|
||||||
let minR = midR * 0.75; // smallest interior fill or single wall width
|
let minR = midR * 0.75; // smallest interior fill or single wall width
|
||||||
let maxR = midR * 1.5; // max double wall extrusion width
|
let maxR = midR * 1.5; // max double wall extrusion width
|
||||||
let maxF = midR * 2; // max interior extrusion width
|
let maxF = midR * 2; // max interior extrusion width
|
||||||
|
|
||||||
|
let { lines, polys } = trace_noodle(noodle, noodleWidth, minR, midR, maxR, {
|
||||||
|
brute: false,
|
||||||
|
showExtrusion: true,
|
||||||
|
showExtrudeInset: true,
|
||||||
|
showChainIntersect: false
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log({ lines, polys });
|
||||||
|
|
||||||
|
POLY.setZ([...lines, ...polys], z);
|
||||||
|
thin.push(...lines);
|
||||||
|
shells.push(...polys);
|
||||||
|
|
||||||
|
return { last, gaps };
|
||||||
|
}
|
||||||
|
|
||||||
|
// trace a single extrusion line around the inside of the noodle
|
||||||
|
function trace_noodle(noodle, noodleWidth, minR, midR, maxR, {
|
||||||
|
brute,
|
||||||
|
showExtrusion,
|
||||||
|
showExtrudeInset,
|
||||||
|
showChainIntersect,
|
||||||
|
}) {
|
||||||
|
let scale = 1000;
|
||||||
|
|
||||||
|
let polys = [];
|
||||||
|
let lines = [];
|
||||||
let insets = [];
|
let insets = [];
|
||||||
let inset;
|
|
||||||
let dstep = minR / 4;
|
let dstep = minR / 4;
|
||||||
let dtotl = 0;
|
let dtotl = 0;
|
||||||
top.gaps = remain;
|
|
||||||
|
|
||||||
if (maxInsetBypass || intersectChains) {
|
let inset = POLY.offset(noodle, -dstep, {
|
||||||
inset = POLY.offset(noodle, -dstep, {
|
join: ClipperLib.JoinType.jtRound,
|
||||||
join: ClipperLib.JoinType.jtRound,
|
arc: (1/dstep) * 4
|
||||||
arc: (1/dstep) * 4
|
});
|
||||||
});
|
while (inset && inset.length) {
|
||||||
while (inset && inset.length) {
|
dtotl += dstep;
|
||||||
dtotl += dstep;
|
for (let p of POLY.flatten(inset, [])) {
|
||||||
for (let p of POLY.flatten(inset, [])) {
|
p.dtotl = dtotl;
|
||||||
p.dtotl = dtotl;
|
|
||||||
}
|
|
||||||
insets.push(inset);
|
|
||||||
let ps = POLY.flatten(inset);
|
|
||||||
POLY.setZ(inset, z);
|
|
||||||
if (showInset) {
|
|
||||||
shells.appendAll(inset);
|
|
||||||
}
|
|
||||||
if (intersectChains) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
inset = POLY.offset(inset, -dstep, {
|
|
||||||
minArea: 0
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
insets.push(inset);
|
||||||
|
polys.appendAll(inset);
|
||||||
|
if (!brute) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
inset = POLY.offset(inset, -dstep, {
|
||||||
|
minArea: 0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// project from first inset and find greated offset intersection
|
// project from first inset and find greated offset intersection
|
||||||
if (maxInsetBypass) {
|
if (brute) {
|
||||||
let { intersectRayLine } = base.util;
|
let { intersectRayLine } = base.util;
|
||||||
let tests = insets.slice(1);
|
let tests = insets.slice(1);
|
||||||
let source = POLY.flatten(insets[0], []);
|
let source = POLY.flatten(insets[0], []);
|
||||||
console.log({ source, tests });
|
|
||||||
for (let poly of source) {
|
for (let poly of source) {
|
||||||
// segment the smallest inset and test against all other insets
|
// segment the smallest inset and test against all other insets
|
||||||
poly.segment(minR).forEachSegment((p1, p2) => {
|
poly.segment(minR).forEachSegment((p1, p2) => {
|
||||||
|
|
@ -263,14 +272,12 @@ function thin_type_3(params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (max.int) {
|
if (max.int) {
|
||||||
thin.push(new Point(np1.x, np1.y, z));
|
lines.push(new Point(np1.x, np1.y));
|
||||||
thin.push(new Point(max.int.x, max.int.y, z));
|
lines.push(new Point(max.int.x, max.int.y));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return { lines, polys };
|
||||||
shells.appendAll(noodle);
|
|
||||||
return { last, gaps };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pointMap = new Map();
|
let pointMap = new Map();
|
||||||
|
|
@ -323,12 +330,10 @@ function thin_type_3(params) {
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
let chains = [];
|
|
||||||
let nusegs = [];
|
let nusegs = [];
|
||||||
|
|
||||||
// for each standalone noodle, construct medial axis
|
// for each standalone noodle, construct medial axis
|
||||||
// map medial axis points into de-duplicated segments
|
// map medial axis points into de-duplicated segments
|
||||||
// which will be re-connected into chains
|
|
||||||
for (let poly of noodle) {
|
for (let poly of noodle) {
|
||||||
// scale the polygon (including inners) so the medial axis code works properly
|
// scale the polygon (including inners) so the medial axis code works properly
|
||||||
poly = poly.clone(true).scale({ x:scale, y:scale, z:scale });
|
poly = poly.clone(true).scale({ x:scale, y:scale, z:scale });
|
||||||
|
|
@ -343,60 +348,23 @@ function thin_type_3(params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect segments into chains
|
// break down long segments
|
||||||
for (let seg of lineMap.values()) {
|
for (let seg of lineMap.values()) {
|
||||||
let { p0, p1 } = seg;
|
let { p0, p1 } = seg;
|
||||||
let len = seg.len = pointDist(p0, p1);
|
let len = seg.len = pointDist(p0, p1);
|
||||||
if (altNuSegs) {
|
// add to nusegs
|
||||||
// add to nusegs
|
if (len <= minR) {
|
||||||
if (len <= minR) {
|
nusegs.push({ p0, p1 });
|
||||||
|
} else {
|
||||||
|
let steps = Math.ceil(len / minR) + 1;
|
||||||
|
let dx = (p1.x - p0.x) / steps;
|
||||||
|
let dy = (p1.y - p0.y) / steps;
|
||||||
|
for (let i=0; i<steps; i++) {
|
||||||
|
p1 = { x:p0.x + dx, y:p0.y + dy };
|
||||||
nusegs.push({ p0, p1 });
|
nusegs.push({ p0, p1 });
|
||||||
} else {
|
p0 = p1;
|
||||||
let steps = Math.ceil(len / minR) + 1;
|
|
||||||
let dx = (p1.x - p0.x) / steps;
|
|
||||||
let dy = (p1.y - p0.y) / steps;
|
|
||||||
for (let i=0; i<steps; i++) {
|
|
||||||
p1 = { x:p0.x + dx, y:p0.y + dy };
|
|
||||||
nusegs.push({ p0, p1 });
|
|
||||||
p0 = p1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (altNuSegs) continue;
|
|
||||||
// for each line segment either add it to a chain or start a new chain
|
|
||||||
let add;
|
|
||||||
for (let chain of chains) {
|
|
||||||
if (p0.count === 2 && p0 === chain[0]) {
|
|
||||||
// prefix chain with p1
|
|
||||||
chain.splice(0,0,p1);
|
|
||||||
add = true;
|
|
||||||
} else if (p0.count === 2 && p0 === chain.peek()) {
|
|
||||||
// append chain with p1
|
|
||||||
chain.push(p1);
|
|
||||||
add = true;
|
|
||||||
} else if (p1.count === 2 && p1 === chain[0]) {
|
|
||||||
// prefix chain with p0
|
|
||||||
chain.splice(0,0,p0);
|
|
||||||
add = true;
|
|
||||||
} else if (p1.count === 2 && p1 === chain.peek()) {
|
|
||||||
// append chain with p0
|
|
||||||
chain.push(p0);
|
|
||||||
add = true;
|
|
||||||
}
|
|
||||||
if (add) {
|
|
||||||
chain.total += len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!add) {
|
|
||||||
chains.push([ p0, p1 ]);
|
|
||||||
chains.peek().total = len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// render inset "noodle"
|
|
||||||
if (showNoodle) {
|
|
||||||
shells.appendAll(noodle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// divide a segment/redius into 1 or more equal subsegments
|
// divide a segment/redius into 1 or more equal subsegments
|
||||||
|
|
@ -422,84 +390,9 @@ function thin_type_3(params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter chains that are too short from consideration
|
|
||||||
// chains = chains.filter(c => c.total >= minR);
|
|
||||||
|
|
||||||
// gather point offsets into shells
|
|
||||||
let zi = z;
|
|
||||||
let nuchains = [];
|
|
||||||
if (!altNuSegs)
|
|
||||||
for (let chain of chains) {
|
|
||||||
let nuchain = [];
|
|
||||||
nuchains.push(nuchain);
|
|
||||||
// zi += 0.1;
|
|
||||||
if (showChainRawPoints)
|
|
||||||
for (let pt of chain) {
|
|
||||||
shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10));
|
|
||||||
}
|
|
||||||
// draw medial axis chain
|
|
||||||
let { closed, length } = chain;
|
|
||||||
let term = closed ? length : length - 1;
|
|
||||||
let segs = [];
|
|
||||||
for (let i=0; i<term; i++) {
|
|
||||||
let p0 = chain[i];
|
|
||||||
let p1 = chain[(i+1) % length];
|
|
||||||
let len = pointDist(p0, p1);
|
|
||||||
// medial axis segment
|
|
||||||
if (showMedLine) {
|
|
||||||
thin.push(new Point(p0.x, p0.y, zi));
|
|
||||||
thin.push(new Point(p1.x, p1.y, zi));
|
|
||||||
}
|
|
||||||
// compute medial axis segment normal
|
|
||||||
let steps = Math.ceil(len / midR) + 1;
|
|
||||||
let step = 1 / steps;
|
|
||||||
let dr = (p1.r - p0.r);
|
|
||||||
let dx = (p1.x - p0.x);
|
|
||||||
let dy = (p1.y - p0.y);
|
|
||||||
let ndx = dx / len;
|
|
||||||
let ndy = dy / len;
|
|
||||||
segs.push({ p0, p1, steps, step, dr, dx, dy, ndx, ndy });
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute chain subdivisions
|
|
||||||
let slen = segs.length;
|
|
||||||
for (let si=0; si<slen; si++) {
|
|
||||||
let seg = segs[si];
|
|
||||||
let segp = segs[(si - 1 + slen) % slen];
|
|
||||||
let { p0, p1, steps, step, dr, dx, dy, ndx, ndy } = seg;
|
|
||||||
let first = si === 0;
|
|
||||||
let last = si === slen - 1;
|
|
||||||
let mid = !(first || last);
|
|
||||||
if (first || mid) {
|
|
||||||
nuchain.push(p0);
|
|
||||||
}
|
|
||||||
// interpolate across the length of the chain segment
|
|
||||||
for (let indx = 1; indx < steps; indx++) {
|
|
||||||
const inc = indx * step;
|
|
||||||
const cr = p0.r + dr * inc;
|
|
||||||
const cx = p0.x + dx * inc;
|
|
||||||
const cy = p0.y + dy * inc;
|
|
||||||
nuchain.push({ x:cx, y:cy, r:cr });
|
|
||||||
}
|
|
||||||
if (last) {
|
|
||||||
nuchain.push(p1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i=0; i<nuchain.length-1; i++) {
|
|
||||||
let p0 = nuchain[i];
|
|
||||||
let p1 = nuchain[i+1];
|
|
||||||
if (!altNuSegs) {
|
|
||||||
nusegs.push({ p0, p1 });
|
|
||||||
}
|
|
||||||
if (showNuMedLine) {
|
|
||||||
thin.push(new Point(p0.x, p0.y, z));
|
|
||||||
thin.push(new Point(p1.x, p1.y, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// project inset segment normals onto closest chain
|
// project inset segment normals onto closest chain
|
||||||
if (intersectChains) for (let insetp of inset) {
|
if (!brute)
|
||||||
|
for (let insetp of inset) {
|
||||||
let { intersectRayLine } = base.util;
|
let { intersectRayLine } = base.util;
|
||||||
let parent = new Polygon();
|
let parent = new Polygon();
|
||||||
let inner = [];
|
let inner = [];
|
||||||
|
|
@ -517,7 +410,7 @@ function thin_type_3(params) {
|
||||||
for (let seg of nusegs) {
|
for (let seg of nusegs) {
|
||||||
let { p0, p1 } = seg;
|
let { p0, p1 } = seg;
|
||||||
let int = intersectRayLine(np1, { dx: dy, dy: -dx }, p0, p1);
|
let int = intersectRayLine(np1, { dx: dy, dy: -dx }, p0, p1);
|
||||||
if (int?.dist < min.dist && int?.dist < offsetN * count) {
|
if (int?.dist < min.dist && int?.dist < noodleWidth) {
|
||||||
min = int;
|
min = int;
|
||||||
min.seg = seg;
|
min.seg = seg;
|
||||||
}
|
}
|
||||||
|
|
@ -525,8 +418,8 @@ function thin_type_3(params) {
|
||||||
if (min.p1) {
|
if (min.p1) {
|
||||||
let mp1 = min.seg;
|
let mp1 = min.seg;
|
||||||
if (showChainIntersect) {
|
if (showChainIntersect) {
|
||||||
thin.push(new Point(np1.x, np1.y));
|
lines.push(new Point(np1.x, np1.y));
|
||||||
thin.push(new Point(min.x, min.y));
|
lines.push(new Point(min.x, min.y));
|
||||||
}
|
}
|
||||||
let mr = min.dist + dstep;
|
let mr = min.dist + dstep;
|
||||||
if (mr < minR) {
|
if (mr < minR) {
|
||||||
|
|
@ -536,7 +429,7 @@ function thin_type_3(params) {
|
||||||
let pop = div(mr);
|
let pop = div(mr);
|
||||||
let odd = (pop.length % 2 === 1);
|
let odd = (pop.length % 2 === 1);
|
||||||
let len = Math.ceil(pop.length / 2);
|
let len = Math.ceil(pop.length / 2);
|
||||||
// check if chain segment was claimed by a different originating segment
|
// check if segment was claimed by a different originating segment
|
||||||
// for odd wall counts so that they're not emitted twice
|
// for odd wall counts so that they're not emitted twice
|
||||||
if (odd && mp1.claimed) len--;
|
if (odd && mp1.claimed) len--;
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
|
|
@ -547,7 +440,7 @@ function thin_type_3(params) {
|
||||||
nupoly.push(new Point(min.x, min.y));
|
nupoly.push(new Point(min.x, min.y));
|
||||||
// for single wall place it exactly on medial axis
|
// for single wall place it exactly on medial axis
|
||||||
if (showExtrusion)
|
if (showExtrusion)
|
||||||
shells.push(newPolygon().centerCircle({
|
polys.push(newPolygon().centerCircle({
|
||||||
x: min.x,
|
x: min.x,
|
||||||
y: min.y
|
y: min.y
|
||||||
}, mr, 12));
|
}, mr, 12));
|
||||||
|
|
@ -557,7 +450,7 @@ function thin_type_3(params) {
|
||||||
for (let i=0; i<1; i++) {
|
for (let i=0; i<1; i++) {
|
||||||
off += (pop[i] * (i ? 2 : 1));
|
off += (pop[i] * (i ? 2 : 1));
|
||||||
if (showExtrusion)
|
if (showExtrusion)
|
||||||
shells.push(newPolygon().centerCircle({
|
polys.push(newPolygon().centerCircle({
|
||||||
x: np1.x + dy * off,
|
x: np1.x + dy * off,
|
||||||
y: np1.y - dx * off
|
y: np1.y - dx * off
|
||||||
}, pop[0], 12));
|
}, pop[0], 12));
|
||||||
|
|
@ -570,20 +463,14 @@ function thin_type_3(params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (showExtrudeInset) {
|
|
||||||
shells.push(...nupoly.clean().simplify());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
{
|
// subtract inner extrusion offset from parent
|
||||||
let cut = POLY.subtract([ parent ], inner, []);
|
if (showExtrudeInset) {
|
||||||
console.log({ parent, cut });
|
polys.push(...POLY.subtract([ parent ], inner, []));
|
||||||
shells.push(...cut);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
POLY.setZ([...thin, ...shells], z);
|
return { lines, polys };
|
||||||
|
|
||||||
return { last, gaps };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue