checkpoint thin3 work

This commit is contained in:
Stewart Allen 2025-09-14 14:29:23 -04:00
commit ba61e39143
2 changed files with 152 additions and 96 deletions

View file

@ -259,8 +259,9 @@ export class Polygon {
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 nood = POLY.nest(POLY.flatten([ clone, ...ins ], [], true)); let nood = POLY.nest(POLY.flatten([ clone, ...ins ], [], true));
return { noodle: nood, remain: ins }; return { noodle: nood, remain };
} }
// generate center crossing point cloud // generate center crossing point cloud

View file

@ -173,17 +173,23 @@ function thin_type_3(params) {
top.gaps = last = remain; top.gaps = last = remain;
let thin = top.thin_fill; let thin = top.thin_fill;
let scale = 5000; let scale = 1000;
let minR = offsetN / 2; let minR = offsetN / 2;
let maxR = minR * 1.5; let maxR = minR * 1.5;
let minSpur = 0; // offsetN * 3; let minSpur = 0; // offsetN * 3;
let mergeClosePoints = true;
let interpolateShortSpur = true;
let pointMap = new Map(); let pointMap = new Map();
let lineMap = new Map(); let lineMap = new Map();
function point2key(point) {
return `${(point.x*100)|0}|${(point.y*100)|0}`;
}
// convert point to deduplicated / unique point record // convert point to deduplicated / unique point record
function pointToRec(point) { function pointToRec(point) {
let key = `${(point.x*100)|0}|${(point.y*100)|0}`; let key = point2key(point);
let rec = pointMap.get(key); let rec = pointMap.get(key);
if (!rec) { if (!rec) {
rec = { rec = {
@ -222,6 +228,9 @@ function thin_type_3(params) {
let chains = []; let chains = [];
// for each standalone noodle, construct medial axis
// 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 });
@ -234,63 +243,75 @@ function thin_type_3(params) {
for (let { point0, point1 } of ma) { for (let { point0, point1 } of ma) {
pointsToLine(point0, point1); pointsToLine(point0, point1);
} }
for (let seg of lineMap.values()) { }
let { p0, p1 } = seg;
let len = seg.len = Math.hypot(p0.x-p1.x, p0.y-p1.y); // connect segments into chains
// filter out lines where both points' radii are under the // console.log({ seg: [...lineMap.values()].sort((s1,s2) => s2.len - s1.len) });
// minR threshold. shorten and fixup lines where minR falls for (let seg of lineMap.values()) {
// somewhere along the gradient between ends. let { p0, p1 } = seg;
if (p0.r < minR || p1.r < minR) { let len = seg.len = Math.hypot(p0.x-p1.x, p0.y-p1.y);
if (p0.r > minR && len > minSpur) { // filter out lines where both points' radii are under the
// move p1 toward p0 until minR met // minR threshold. shorten and fixup lines where minR falls
let pct = (minR - p1.r) / (p0.r - p1.r); // somewhere along the gradient between ends.
let dx = p1.x - p0.x; if (interpolateShortSpur)
let dy = p1.y - p0.y; if (p0.r < minR || p1.r < minR) {
p1.x -= dx * pct; if (p0.r > minR && len > minSpur) {
p1.y -= dy * pct; // move p1 toward p0 until minR met
p1.r = minR; let pct = (minR - p1.r) / (p0.r - p1.r);
pointMap.delete(p1.key); let dx = p1.x - p0.x;
} else if (p1.r > minR && len > minSpur) { let dy = p1.y - p0.y;
// move p0 toward p1 until minR met p1.x -= dx * pct;
let pct = (minR - p0.r) / (p1.r - p0.r); p1.y -= dy * pct;
let dx = p0.x - p1.x; p1.r = minR;
let dy = p0.y - p1.y; pointMap.delete(p1.key);
p0.x -= dx * pct; } else if (p1.r > minR && len > minSpur) {
p0.y -= dy * pct; // move p0 toward p1 until minR met
p0.r = minR; let pct = (minR - p0.r) / (p1.r - p0.r);
pointMap.delete(p0.key); let dx = p0.x - p1.x;
} else { let dy = p0.y - p1.y;
lineMap.delete(seg.key); p0.x -= dx * pct;
continue; p0.y -= dy * pct;
} p0.r = minR;
pointMap.delete(p0.key);
} else {
// both points eliminated
pointMap.delete(p0.key);
pointMap.delete(p1.key);
lineMap.delete(seg.key);
continue;
} }
// for each line segment either add it to a chain or start a new chain }
let add; // for each line segment either add it to a chain or start a new chain
for (let chain of chains) { let add;
if (p0.count === 2 && p0.key === chain[0].key) { for (let chain of chains) {
chain.splice(0,0,{...p1, len}); if (p0.count === 2 && p0.key === chain[0].key) {
} else if (p0.count === 2 && p0.key === chain.peek().key) { // prefix chain with p1
chain.peek().len = len; chain.splice(0,0,{...p1, len});
chain.push({...p1}); add = true;
add = true; } else if (p0.count === 2 && p0.key === chain.peek().key) {
} else if (p1.count === 2 && p1.key === chain[0].key) { // append chain with p1
chain.splice(0,0,{...p0, len}); chain.peek().len = len;
add = true; chain.push({...p1});
} else if (p1.count === 2 && p1.key === chain.peek().key) { add = true;
chain.peek().len = len; } else if (p1.count === 2 && p1.key === chain[0].key) {
chain.push({...p0}); // prefix chain with p0
add = true; chain.splice(0,0,{...p0, len});
} add = true;
if (add) { } else if (p1.count === 2 && p1.key === chain.peek().key) {
chain.total += len; // append chain with p0
break; chain.peek().len = len;
} chain.push({...p0});
add = true;
} }
if (!add) { if (add) {
chains.push([ {...p0 ,len}, {...p1} ]); chain.total += len;
chains.peek().total = len; break;
} }
} }
if (!add) {
chains.push([ {...p0 ,len}, {...p1} ]);
chains.peek().total = len;
}
} }
// todo: merge chains at next of 3 or more preferencing // todo: merge chains at next of 3 or more preferencing
@ -300,7 +321,7 @@ function thin_type_3(params) {
let showNoodle = false; let showNoodle = false;
let showMid = true; let showMid = true;
let showCross = false; let showCross = false;
let showRad = true; let showRad = false;
let showShells = false; let showShells = false;
// render inset "noodle" // render inset "noodle"
@ -327,10 +348,16 @@ function thin_type_3(params) {
} }
// filter chains that are too short from consideration // filter chains that are too short from consideration
chains = chains.filter(c => c.total >= minR).map(chain => { chains = chains.filter(c => c.total >= minR);
chains.sort((c1,c2) => c2.total - c1.total);
// merge chain points that are too close
if (mergeClosePoints)
chains = chains.map(chain => {
// chain reduction by merging short segments // chain reduction by merging short segments
let end = chain[0]; let end = chain[0];
let nuchain = [ end ]; let nuchain = [ end ];
nuchain.total = chain.total;
for (let point of chain.slice(1)) { for (let point of chain.slice(1)) {
if (point.len === undefined || point.len + end.len >= minR) { if (point.len === undefined || point.len + end.len >= minR) {
nuchain.push(point); nuchain.push(point);
@ -342,9 +369,37 @@ function thin_type_3(params) {
return nuchain; return nuchain;
}); });
// 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] = [];
if (rec.indexOf(chain) < 0) rec.push(chain);
}
for (let chain of chains) {
addEp(chain[0], chain);
addEp(chain.peek(), chain);
}
// mark spurs
for (let [key,val] of Object.entries(nexus)) {
if (val.length === 1) {
val[0].spur = true;
delete nexus[key];
}
}
// detach spurs from nexus and shorten by ?
for (let [key,val] of Object.entries(nexus)) {
nexus[key] = val.filter(c => !c.spur);
}
console.log({ chains, nexus });
// 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)
console.log(chains); // map chain endpoints to nexus points
// gather point offsets into shells // gather point offsets into shells
// todo: order outer shell innersection to inner // todo: order outer shell innersection to inner
@ -410,43 +465,43 @@ function thin_type_3(params) {
} }
// follow shells and output lines // follow shells and output lines
console.log({ shells }); // console.log({ shells });
if (showShells) // if (showShells)
for (let shell = 1; shell <= 1; shell++) { // for (let shell = 1; shell <= 1; shell++) {
let poly = new Polygon().setOpen(); // let poly = new Polygon().setOpen();
let arr = shells[shell]; // let arr = shells[shell];
let lp = arr[0]; // let lp = arr[0];
poly.push(lp); // poly.push(lp);
let find; // let find;
for (;;) { // for (;;) {
find = { dist: Infinity }; // find = { dist: Infinity };
// find next closest point // // find next closest point
for (let i=1; i<arr.length; i++) { // for (let i=1; i<arr.length; i++) {
let pt = arr[i]; // let pt = arr[i];
if (!pt) continue; // if (!pt) continue;
let dist = lp.distTo2D(pt); // let dist = lp.distTo2D(pt);
if (dist < find.dist) { // if (dist < find.dist) {
find.dist = dist; // find.dist = dist;
find.pi = i; // find.pi = i;
find.pt = pt; // find.pt = pt;
} // }
} // }
if (find.pt) { // if (find.pt) {
if (find.dist > 1) { // if (find.dist > 1) {
top.shells.push(poly); // top.shells.push(poly);
poly = new Polygon().setOpen(); // poly = new Polygon().setOpen();
} // }
arr[find.pi] = undefined; // arr[find.pi] = undefined;
poly.push(lp = find.pt); // poly.push(lp = find.pt);
} else { // } else {
break; // break;
} // }
} // }
console.log({ poly }); // console.log({ poly });
if (poly.length > 1) { // if (poly.length > 1) {
top.shells.push(poly); // top.shells.push(poly);
} // }
} // }
return { last, gaps }; return { last, gaps };
} }