@davepagurek/flo-mat
Version:
Medial / Scale Axis Transform (MAT/SAT) Library.
99 lines (74 loc) • 2.65 kB
text/typescript
import { evalDeCasteljau } from "flo-bezier3";
import { dot, lineLineIntersection, squaredDistanceBetween } from "flo-vector2d";
import { BezierPiece } from '../mat/bezier-piece.js';
/**
* @internal
*
* Reduces the circle radius initially as an optimization step.
*/
function reduceRadius(
extreme: number,
bezierPieces: BezierPiece[],
p: number[],
x: number[]) {
const TOLERANCE = 1 + 2**-10;
let prevP: number[] | undefined = undefined;
let minRadius = Number.POSITIVE_INFINITY;
for (let i=0; i<bezierPieces.length; i++) {
const bezierPiece = bezierPieces[i];
const ps = bezierPiece.curve.ps;
let r1 = Number.POSITIVE_INFINITY;
const p1 = evalDeCasteljau(ps, bezierPiece.ts[0]);
// Prevent evaluating the same points twice
if (!prevP || prevP[0] !== p1[0] || prevP[1] !== p1[1]) {
const cc1 = getCircleCenterFrom2PointsAndNormal(extreme, p, x, p1);
if (cc1) { r1 = squaredDistanceBetween(p, cc1); }
}
let r2 = Number.POSITIVE_INFINITY;
const p2 = evalDeCasteljau(ps, bezierPiece.ts[1]);
const cc2 = getCircleCenterFrom2PointsAndNormal(extreme, p, x, p2);
if (cc2) { r2 = squaredDistanceBetween(p, cc2); }
prevP = p2;
const d = Math.min(r1, r2);
if (d < minRadius) {
minRadius = d;
}
}
// The extra bit is to account for floating point precision.
return TOLERANCE*minRadius;
}
/**
* @internal
* @param p A point on the circle with normal pointing to x towards the center
* of the circle.
* @param x
* @param p1 Another point on the circle.
*/
function getCircleCenterFrom2PointsAndNormal(
extreme: number,
p: number[],
x: number[],
p1: number[]) {
const TOLERANCE = (2**-14*extreme)**2;
// Ignore if p and p1 are too close together
if (squaredDistanceBetween(p,p1) < TOLERANCE) {
return undefined;
}
/** The perpindicular bisector between the two given points on the circle */
const pb = [
(p[0] + p1[0]) / 2,
(p[1] + p1[1]) / 2,
];
const tan = [p1[0] - p[0], p1[1] - p[1]];
const norm = [-tan[1], tan[0]]; // Rotate by 90 degrees
const pb2 = [pb[0] + norm[0], pb[1] + norm[1]];
const res = lineLineIntersection([p,x], [pb, pb2]);
if (!res) { return undefined; }
const resO = [res[0] - p[0], res[1] - p[1]];
const xO = [x[0] - p[0], x[1] - p[1]];
if (dot(resO, xO) < 0) {
return undefined;
}
return res;
}
export { reduceRadius }