UNPKG

@davepagurek/flo-mat

Version:

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

105 lines (85 loc) 3.91 kB
import { eAdd, eEstimate, eMult, eNegativeOf, eProduct, orient2d, twoDiff } from 'big-float-ts'; import { dot } from "flo-vector2d"; import { compareCurvaturesAtInterface } from "./compare-curvatures-at-interface.js"; /** * Returns a positive value if the second bezier (of order 1, 2 or 3) curves * anti-clockwise with respect to the first at the point where the first bezier * ends and the second one starts. Returns a negative number if the turn is * clockwise. Returns 0 otherwise. * * The algorithm is a generalization of `ccw`, a.k.a `orient2d`. * * The above obviously necessitates that their endpoints coincide as described. * * Preconditions (for robustness): * * The beziers has control points with max bit-length of 25 and bit-aligned. * * The bezier does not have infinite curvature at either endpoint * * This is so the vectors between control points can be * calculated exactly without resorting to adaptive infinite precision floating * point operations. Note: aligned to 'grid' here means if you bitwise-and all * values together the resulting bitlength === the max bithlength of any value. * * @param psI The incoming bezier that ends at the interface * @param psO The outgoing bezier that starts at the interface */ // TODO - improve and make at least 46-bitlength precondition function getInterfaceCcw(psI: number[][], psO: number[][]) { const lenI = psI.length; // second last control point of incoming curve const p0 = psI[lenI-2]; // last control point of incoming curve / first control point of outgoing const p1 = psO[0]; // second control point of outgoing curve const p2 = psO[1]; // Max one bit can be added in the calculations below due to bit-alignment const xE = p1[0] - p0[0]; // tangent x-coordinate const yE = p1[1] - p0[1]; // tangent y-coordinate const xS = p2[0] - p1[0]; // tangent x-coordinate const yS = p2[1] - p1[1]; // tangent y-coordinate // If the tangent is to be found at t === 0 or t === 1 then using a basic // property of bezier curves we can find the tangents easily as below // (non-normalized) tangent of incoming curve at t === 1 const tangentAtEnd = [xE,yE]; // (non-normalized) tangent of outgoing curve at t === 0 const tangentAtStart = [xS,yS]; // const crossTangents = orient2d(p0, p1, p2); const crossTangents = orient2dPrecise(p0, p1, p2); if (crossTangents !== 0) { return crossTangents; } // The dot calculated below will have a max bitlength of // (2*(maxBitLength + 1)) + 1 === e.g. (2*(25 + 1)) + 1 === 53 // If the preconditions are met it is exact const dotTangents = dot(tangentAtEnd, tangentAtStart); if (dotTangents > 0) { // Curves go in same direction at interface - neither clock or // anti-clockwise. // Note: The above comment is not strictly true but as this case is not // important for the algorithm we return 0 return 0; } // Curves go in opposite directions at interface starting off with the exact // same tangent - look now at curvature to see which has the largest // curvature so we can base the clock or anti-clockwise result on that // Look at curvature return compareCurvaturesAtInterface(psI.slice().reverse(), psO); } /** Returns the cross from A to B to C */ function orient2dPrecise( A: number[], B: number[], C: number[]) { // const detleft = (A[0] - C[0]) * (B[1] - C[1]); // const detright = (A[1] - C[1]) * (B[0] - C[0]); // const det = detleft - detright; const a = twoDiff(A[0],C[0]); const b = twoDiff(B[1],C[1]); const c = twoDiff(A[1],C[1]); const d = twoDiff(B[0],C[0]); const e = eMult(a,b); const f = eMult(c,d); const g = eAdd(e,eNegativeOf(f)); return eEstimate(g); } export { getInterfaceCcw }