import type { RootInterval } from 'flo-poly'; import type { CurvePiece } from "../../mat/curve-piece.js"; import type { PrePointOnShape } from "../../point-on-shape/point-on-shape.js"; import type { AntipodalPoint } from './antipodal-point.js'; import { ddMultDouble1 } from 'double-double'; import { lengthSquared } from "flo-vector2d"; import { roots, ddHorner } from 'flo-poly'; import { evalDeCasteljau } from 'flo-bezier3'; import { ddGetMedialPointCoeffs } from '../../get-medial-points/double-double/dd-get-medial-point-coeffs.js'; import { ddGetMedialPointCoeffsBez2_SameCurve } from '../../get-medial-points/double-double/dd-get-medial-point-coeffs-bez2-same-curve.js'; import { ddGetMedialPointCoeffsBez3_SameCurve } from '../../get-medial-points/double-double/dd-get-medial-point-coeffs-bez3-same-curve.js'; import { ddGetMedialPointCoeffsBez0 } from '../../get-medial-points/double-double/dd-get-medial-point-coeffs-bez0.js'; const { sqrt, abs } = Math; const qmd = ddMultDouble1; /** * @param maxCoordPowerOf2 * @param yPos The point on the shape * @param for1Prong defaults to `false`; * @param angle defaults to `0`; * @param curvePiece The curve piece * * @internal */ function getClosestPoint( maxCoordPowerOf2: number, nnormDd: number[][], yPos: PrePointOnShape, for1Prong: boolean, angle: number, curvePiece: CurvePiece): AntipodalPoint[] { const { curve, ts: [tS,tE] } = curvePiece; const { ps } = curve; const { curve: touchedCurve, t, p: y, pDd: yDd } = yPos; const isTouched = curve === touchedCurve; const shouldDeflate = angle === 0 && isTouched; const infos: AntipodalPoint[] = []; let startPushed = false; let endPushed = false; let ss: RootInterval[]; if (tS !== tE && !(shouldDeflate && ps.length === 2) && !(isTouched && ps.length === 3 && for1Prong)) { const { A, B, H } = shouldDeflate ? ps.length === 3 ? ddGetMedialPointCoeffsBez2_SameCurve(t, nnormDd, ps) : ddGetMedialPointCoeffsBez3_SameCurve(t, nnormDd, ps) : ddGetMedialPointCoeffs(yDd, nnormDd, ps); // const Hd = H.map(h => h[0] + h[1]); ss = roots(H, tS, tE) || []; //----------------------- for (let i=0; i 1) { continue; } if (sE < tS || sS > tE) { continue; } // outside curve piece if (sS < 0) { continue; } // outside curve piece // clip to [0,1] const s = sE > 1 ? 1 : _s; //----------------------- // const AS = Horner(A, s); // const BS = Horner(B, s); const ASDd = ddHorner(A, s); const BSDd = ddHorner(B, s); const AS = ASDd[0] + ASDd[1]; const BS = BSDd[0] + BSDd[1]; // const CS = Horner(C, s); // const DS = Horner(D, s); const _AS = abs(AS); // const _CS = abs(CS); if (_AS < 2**-40) { continue; } // const w = _AS > _CS // ? -BS / AS // : -DS / CS; // alternative const w = -BS / AS; if (w <= 0) { // If `w` is negative the circle radius is negative -> discard continue; } const VDd = [qmd(w, nnormDd[0]), qmd(w, nnormDd[1])]; const V = [VDd[0][1], VDd[1][1]]; const d = sqrt(lengthSquared(V)); if (d <= 2**(maxCoordPowerOf2 - 40)) { // If `w` <= 2**-40 -> discard continue; } const x = [y[0] + V[0], y[1] + V[1]]; //----------------------- if (ri.tS <= tS && ri.tE >= tS) { startPushed = true; } if (ri.tS <= tE && ri.tE >= tE) { endPushed = true; } // If the point is the first control point of the curve then we // ensure the point becomes the last control point of the prior // curve to induce some symmetery and simplify the algorithm. const s_ = s === 0 ? 1 : s; const curve_ = s === 0 ? curve.prev : curve; const info: AntipodalPoint = { curve: curve_, s: s_, d, x }; infos.push(info); } } //------------------------------------------ const pLast = ps[ps.length - 1]; const dontPush1 = ( (t === 0 && curve === touchedCurve!.prev) || (t === 1 && curve === touchedCurve) ); //-------------------------------------- // Also check curve start control point //-------------------------------------- const PS: { P: number[], t: number }[] = []; if (tS === 0) { // We never check points at `t === 0` since they are covered by the previous curve's endpoint // which cannot be culled if this curve`s `t === 0` is included } else if (tS === 1) { if (!dontPush1 && !startPushed) { PS.push({ P: pLast, t: 1 }); } } else { if (!startPushed) { const pp = evalDeCasteljau(ps, tS); PS.push({ P: pp, t: tS }); } } //-------------------------------------- // Also check curve end control point //-------------------------------------- if (tS !== tE) { if (tE === 0) { // We never check points at `t === 0` since they are covered by the previous curve's endpoint // which cannot be culled if this curve`s `t === 0` is included } else if (tE === 1) { if (!dontPush1 && !endPushed) { PS.push({ P: pLast, t: 1 }); } } else { if (!endPushed) { const pp = evalDeCasteljau(ps, tE); PS.push({ P: pp, t: tE }); } } } //------------------------------------------ for (let i=0; i