@davepagurek/flo-mat
Version:
Medial / Scale Axis Transform (MAT/SAT) Library.
111 lines (89 loc) • 3.71 kB
text/typescript
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 }