@davepagurek/flo-mat
Version:
Medial / Scale Axis Transform (MAT/SAT) Library.
98 lines (75 loc) • 2.41 kB
text/typescript
import { tangent } from 'flo-bezier3';
import { cross, toUnitVector } from 'flo-vector2d';
import { CpNode, getChildren, isFullyTerminating, isTerminating } from '../cp-node/cp-node.js';
import { getCurveToNext } from '../cp-node/get-curve-to-next.js';
/** @internal */
const defaultTolerance = 1; // 1 degree
/**
* Traverses all edges (depth first) of the given MAT tree starting at the given
* vertex (represented by a [[CpNode]]). Returns the result as an array of
* smooth branches, where smoothness is total or within a given tolerance.
* @param cpNode Any [[CpNode]] representing the start vertex.
* @param tolerance 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.
*/
function getBranches(
cpNode: CpNode,
tolerance: number = defaultTolerance): CpNode[][] {
// Start from a leaf
while (!isFullyTerminating(cpNode)) {
cpNode = cpNode.next;
}
const branches: CpNode[][] = [];
// Since the tree is unrooted we must iterate in all directions from the
// given vertex.
const cps = [cpNode.prevOnCircle];
let branchCpNodes: CpNode[] = [];
while (cps.length) {
const cp = cps.pop()!;
branchCpNodes.push(cp);
let children = getChildren(cp);
if (isFullyTerminating(cp)) {
if (branchCpNodes.length > 1) {
branches.push(branchCpNodes);
}
branchCpNodes = [];
continue;
}
if (children.length === 1) {
cps.push(children[0]);
continue;
}
children = children.filter(cpNode => !isTerminating(cpNode));
if (children.length === 0) {
branches.push(branchCpNodes);
branchCpNodes = [];
continue;
}
if (children.length > 1) {
// More than one branch comes together
branches.push(branchCpNodes);
branchCpNodes = [];
cps.push(...children);
continue;
}
const backPointingTan = toUnitVector(tangent(
getCurveToNext(cp.next.prevOnCircle),
0
));
const forwardPointingTan = toUnitVector(tangent(
getCurveToNext(children[0]),
0
));
const cross_ = cross(backPointingTan, forwardPointingTan);
const angle = Math.abs(Math.asin( cross_ ) * (180/Math.PI));
if (angle > tolerance) {
branches.push(branchCpNodes);
branchCpNodes = [];
}
cps.push(children[0]);
}
if (branchCpNodes.length) { branches.push(branchCpNodes); }
return branches;
}
export { getBranches }