fix thin3 chain marking and shortening

This commit is contained in:
Stewart Allen 2025-09-15 17:11:56 -04:00
commit 32d4b7e62a
2 changed files with 86 additions and 39 deletions

View file

@ -258,7 +258,7 @@ export class Polygon {
// of the polygon with the noodle removed (for the next pass) // of the polygon with the noodle removed (for the next pass)
noodle(width) { noodle(width) {
let clone = this.clone(true); let clone = this.clone(true);
let ins = clone.offset(width); let ins = clone.offset(width) ?? [];
let remain = ins.clone(true); let remain = ins.clone(true);
let nood = POLY.nest(POLY.flatten([ clone, ...ins ], [], true)); let nood = POLY.nest(POLY.flatten([ clone, ...ins ], [], true));
return { noodle: nood, remain }; return { noodle: nood, remain };

View file

@ -182,11 +182,13 @@ function thin_type_3(params) {
let mergeChains = true; let mergeChains = true;
let interpolateShortSpur = true; let interpolateShortSpur = true;
let showChainInterpPoints = true; let showChainInterpPoints = true;
let showCross = false;
let showPoint = true;
let showChainRawPoints = false; let showChainRawPoints = false;
let showNoodle = false; let showNoodle = false;
let showMid = true; let showMid = true;
let showCross = true;
let showRad = false; let showRad = false;
let showNexus = false;
let pointMap = new Map(); let pointMap = new Map();
let lineMap = new Map(); let lineMap = new Map();
@ -354,37 +356,6 @@ function thin_type_3(params) {
chains = chains.filter(c => c.total >= minR); chains = chains.filter(c => c.total >= minR);
chains.sort((c1,c2) => c2.total - c1.total); chains.sort((c1,c2) => c2.total - c1.total);
// map chains to nexus via endpoints
let nexus = {};
function addEp(point, chain) {
let key = point.key;
let rec = nexus[key];
if (!rec) rec = nexus[key] = { point, chains: [] };
if (rec.chains.indexOf(chain) < 0) rec.chains.push(chain);
}
// since chains are sorted longest to shortest,
// they will appear in the nexus record longest to shortest
for (let chain of chains) {
addEp(chain[0], chain);
addEp(chain.peek(), chain);
}
// mark spurs
for (let [key, rec] of Object.entries(nexus)) {
if (rec.chains.length === 1) {
rec.chains[0].spur = true;
delete nexus[key];
// console.log({ delete_nexus: key });
}
}
// detach spurs from nexus and shorten by ?
// for (let [key,val] of Object.entries(nexus)) {
// nexus[key].chains = val.chains.filter(c => !c.spur);
// }
console.log({ chains, nexus });
function merge1chain() { function merge1chain() {
let clen = chains.length; let clen = chains.length;
for (let i=0; i<clen; i++) { for (let i=0; i<clen; i++) {
@ -423,6 +394,25 @@ function thin_type_3(params) {
return false; return false;
} }
// console.log({ chains });
// map chains to nexus via endpoints
let nexus = {};
function addEp(point, chain) {
let key = point.key;
let rec = nexus[key];
if (!rec) rec = nexus[key] = { point, chains: [] };
if (rec.chains.indexOf(chain) < 0) rec.chains.push(chain);
}
// since chains are sorted longest to shortest,
// they will appear in the nexus record longest to shortest
for (let chain of chains) {
addEp(chain[0], chain);
addEp(chain.peek(), chain);
}
// console.log({ chains, nexus, np: Object.values(nexus).length });
// connect chains end to end following nexus branches that result // connect chains end to end following nexus branches that result
// in the longest final merged chain (open or closed) // in the longest final merged chain (open or closed)
// map chain endpoints to nexus points // map chain endpoints to nexus points
@ -432,13 +422,69 @@ function thin_type_3(params) {
console.log({ chains }); console.log({ chains });
// mark closed chains // mark closed chains
// todo use exact point match by eliminating slice above
// then later removing repeated points in the resulting chain from merges
for (let chain of chains) { for (let chain of chains) {
chain.closed = pointDist(chain[0], chain.peek()) <= offsetN; chain.closed = pointDist(chain[0], chain.peek()) <= offsetN;
// console.log(chain.closed);
}
function shorten(chain) {
for (let rem=0, i=chain.length-1; i>=1 && rem < minR; i--) {
let c1 = chain[i];
let c2 = chain[i-1];
let d = pointDist(c1,c2);
if (rem + d <= minR) {
chain.pop();
rem += d;
} else {
let diff = minR - rem;
// move c1 toward c2
let pct = diff / d;
let dx = c1.x - c2.x;
let dy = c1.y - c2.y;
chain[i] = {
x: c1.x - dx * pct,
y: c1.y - dy * pct,
r: c1.r
};
rem = 0;
break;
}
}
}
// add chains
// shorten chains terminating at nexus by midR
for (let rec of Object.values(nexus)) {
let { point } = rec;
rec.shorts = 0;
if (rec.chains.length < 3) continue;
for (let chain of chains.filter(c => !c.closed)) {
if (pointDist(chain[0], point) < minR) {
chain.reverse();
shorten(chain);
chain.reverse();
rec.shorts++;
} else if (chain.peek() === point) {
shorten(chain);
rec.shorts++;
}
}
}
if (showNexus)
for (let rec of Object.values(nexus)) {
let { point, chains } = rec;
// console.log(rec);
top.shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts));
} }
// gather point offsets into shells // gather point offsets into shells
// todo: order outer shell innersection to inner let zi = z;
for (let chain of chains) { for (let chain of chains) {
// zi += 0.1;
if (showChainRawPoints) if (showChainRawPoints)
for (let pt of chain) { for (let pt of chain) {
top.shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10)); top.shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10));
@ -447,8 +493,8 @@ function thin_type_3(params) {
for (let i=0; i<chain.length-1; i++) { for (let i=0; i<chain.length-1; i++) {
// medial axis segment // medial axis segment
if (showMid) { if (showMid) {
thin.push(new Point(chain[i].x, chain[i].y)); thin.push(new Point(chain[i].x, chain[i].y, zi));
thin.push(new Point(chain[i+1].x, chain[i+1].y)); thin.push(new Point(chain[i+1].x, chain[i+1].y, zi));
} }
// compute medial axis segment cross section // compute medial axis segment cross section
let p0 = chain[i]; let p0 = chain[i];
@ -474,8 +520,8 @@ function thin_type_3(params) {
if (showCross) { if (showCross) {
thin.push(new Point(cx + yo, cy - xo)); thin.push(new Point(cx + yo, cy - xo));
thin.push(new Point(cx - yo, cy + xo)); thin.push(new Point(cx - yo, cy + xo));
} else { } else if (showPoint) {
top.shells.push(newPolygon().centerCircle({ x:cx, y:cy }, 0.15, 10)); top.shells.push(newPolygon().centerCircle({ x:cx, y:cy }, 0.05, 10));
} }
if (showRad) { if (showRad) {
const Sx = cx + ndy * cr; const Sx = cx + ndy * cr;
@ -495,7 +541,8 @@ function thin_type_3(params) {
} }
} }
POLY.setZ([...thin, ...top.shells], z); // POLY.setZ([...thin, ...top.shells], z);
POLY.setZ([...top.shells], z);
return { last, gaps }; return { last, gaps };
} }