UNPKG

@davepagurek/flo-mat

Version:

Medial / Scale Axis Transform (MAT/SAT) Library.

111 lines (89 loc) 3.71 kB
import { closestPointOnBezier, hausdorffDistance, toCubic } from 'flo-bezier3'; import { CpNode, isTerminating } from '../cp-node/cp-node.js'; import { getBranches } from './get-branches.js'; import { getCurveToNext } from '../cp-node/get-curve-to-next.js'; import { getCurveBetween } from '../get-curve/get-curve-between.js'; /** * Simplifies the given MAT by replacing the piecewise quad beziers composing * the MAT with fewer ones to within a given tolerance. Returns the map of * to be deleted nodes only - does not actually delete them. Use simplifyMat * instead if you want to delete the nodes. * @param cpNode A representation of the MAT * @param anlgeTolerance Tolerance given as the degrees difference of the unit * direction vectors at the interface between curves. A tolerance of zero means * perfect smoothness is required - defaults to 15. * @param hausdorffTolerance The approximate maximum Hausdorff Distance tolerance - * defaults to 0.1 * @param hausdorffSpacing The spacing on the curves used to calculate the Hausdorff * Distance - defaults to 1 */ function simplifyMatMapOnly( cpNode: CpNode, anlgeTolerance = 15, hausdorffTolerance = 1e-1, hausdorffSpacing = 1e0) { const simpleMap: Map<CpNode, { ps: number[][], ts: number[] }> = new Map(); // Start from a leaf while (!isTerminating(cpNode)) { cpNode = cpNode.next; } const branches = getBranches(cpNode, anlgeTolerance); const canDeletes: CpNode[] = []; for (let k=0; k<branches.length; k++) { const branch = branches[k]; // Try to remove some let j = 0; while (j < branch.length) { const i = j; while (true) { j++; if (j === branch.length) { break; } const hd = getTotalHausdorffDistance(i, j, branch, hausdorffSpacing); if (hd > hausdorffTolerance) { break; } else { canDeletes.push(branch[j]); } } if (i + 1 === j) { // no simplification occured } else { const branStart = branch[i]; const branEnd = branch[j-1]; const medial = toCubic(getCurveBetween(branStart, branEnd.next)); const rev = medial.slice().reverse(); let curCpNode = branStart; let prevT = 0; while (curCpNode !== branEnd) { const t = closestPointOnBezier(medial, curCpNode.next.cp.circle.center).t; simpleMap.set(curCpNode, { ps: medial, ts: [prevT, t] }); const oppositeCpNode = curCpNode.nextOnCircle.prev; simpleMap.set(oppositeCpNode, { ps: rev, ts: [1-t, 1-prevT] }); prevT = t; curCpNode = curCpNode.next; } simpleMap.set(curCpNode, { ps: medial, ts: [prevT, 1] }); const oppositeCpNode = curCpNode.nextOnCircle.prev; simpleMap.set(oppositeCpNode, { ps: rev, ts: [0, 1-prevT] }); } } } return { simpleMap, cpNode }; } function getTotalHausdorffDistance( i: number, j: number, branch: CpNode[], hausdorffSpacing: number) { const hds: number[] = []; const longCurve = getCurveBetween(branch[i], branch[j].next); for (let m=i; m<j+1; m++) { hds.push(hausdorffDistance( getCurveToNext(branch[m]), longCurve, hausdorffSpacing )); } return Math.max(...hds); } export { simplifyMatMapOnly }