|
|
|
@ -21,20 +21,9 @@ export function curve<Point extends GlobalPoint | LocalPoint>( |
|
|
|
return [a, b, c, d] as Curve<Point>; |
|
|
|
return [a, b, c, d] as Curve<Point>; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function gradient( |
|
|
|
function solveWithAnalyticalJacobian<Point extends GlobalPoint | LocalPoint>( |
|
|
|
f: (t: number, s: number) => number, |
|
|
|
curve: Curve<Point>, |
|
|
|
t0: number, |
|
|
|
lineSegment: LineSegment<Point>, |
|
|
|
s0: number, |
|
|
|
|
|
|
|
delta: number = 1e-6, |
|
|
|
|
|
|
|
): number[] { |
|
|
|
|
|
|
|
return [ |
|
|
|
|
|
|
|
(f(t0 + delta, s0) - f(t0 - delta, s0)) / (2 * delta), |
|
|
|
|
|
|
|
(f(t0, s0 + delta) - f(t0, s0 - delta)) / (2 * delta), |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function solve( |
|
|
|
|
|
|
|
f: (t: number, s: number) => [number, number], |
|
|
|
|
|
|
|
t0: number, |
|
|
|
t0: number, |
|
|
|
s0: number, |
|
|
|
s0: number, |
|
|
|
tolerance: number = 1e-3, |
|
|
|
tolerance: number = 1e-3, |
|
|
|
@ -48,33 +37,75 @@ function solve( |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const y0 = f(t0, s0); |
|
|
|
// Compute bezier point at parameter t0
|
|
|
|
const jacobian = [ |
|
|
|
const bt = 1 - t0; |
|
|
|
gradient((t, s) => f(t, s)[0], t0, s0), |
|
|
|
const bt2 = bt * bt; |
|
|
|
gradient((t, s) => f(t, s)[1], t0, s0), |
|
|
|
const bt3 = bt2 * bt; |
|
|
|
]; |
|
|
|
const t0_2 = t0 * t0; |
|
|
|
const b = [[-y0[0]], [-y0[1]]]; |
|
|
|
const t0_3 = t0_2 * t0; |
|
|
|
const det = |
|
|
|
|
|
|
|
jacobian[0][0] * jacobian[1][1] - jacobian[0][1] * jacobian[1][0]; |
|
|
|
const bezierX = |
|
|
|
|
|
|
|
bt3 * curve[0][0] + |
|
|
|
|
|
|
|
3 * bt2 * t0 * curve[1][0] + |
|
|
|
|
|
|
|
3 * bt * t0_2 * curve[2][0] + |
|
|
|
|
|
|
|
t0_3 * curve[3][0]; |
|
|
|
|
|
|
|
const bezierY = |
|
|
|
|
|
|
|
bt3 * curve[0][1] + |
|
|
|
|
|
|
|
3 * bt2 * t0 * curve[1][1] + |
|
|
|
|
|
|
|
3 * bt * t0_2 * curve[2][1] + |
|
|
|
|
|
|
|
t0_3 * curve[3][1]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Compute line point at parameter s0
|
|
|
|
|
|
|
|
const lineX = |
|
|
|
|
|
|
|
lineSegment[0][0] + s0 * (lineSegment[1][0] - lineSegment[0][0]); |
|
|
|
|
|
|
|
const lineY = |
|
|
|
|
|
|
|
lineSegment[0][1] + s0 * (lineSegment[1][1] - lineSegment[0][1]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Function values
|
|
|
|
|
|
|
|
const fx = bezierX - lineX; |
|
|
|
|
|
|
|
const fy = bezierY - lineY; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
error = Math.abs(fx) + Math.abs(fy); |
|
|
|
|
|
|
|
|
|
|
|
if (det === 0) { |
|
|
|
if (error < tolerance) { |
|
|
|
return null; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const iJ = [ |
|
|
|
// Analytical derivatives
|
|
|
|
[jacobian[1][1] / det, -jacobian[0][1] / det], |
|
|
|
const dfx_dt = |
|
|
|
[-jacobian[1][0] / det, jacobian[0][0] / det], |
|
|
|
-3 * bt2 * curve[0][0] + |
|
|
|
]; |
|
|
|
3 * bt2 * curve[1][0] - |
|
|
|
const h = [ |
|
|
|
6 * bt * t0 * curve[1][0] - |
|
|
|
[iJ[0][0] * b[0][0] + iJ[0][1] * b[1][0]], |
|
|
|
3 * t0_2 * curve[2][0] + |
|
|
|
[iJ[1][0] * b[0][0] + iJ[1][1] * b[1][0]], |
|
|
|
6 * bt * t0 * curve[2][0] + |
|
|
|
]; |
|
|
|
3 * t0_2 * curve[3][0]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dfy_dt = |
|
|
|
|
|
|
|
-3 * bt2 * curve[0][1] + |
|
|
|
|
|
|
|
3 * bt2 * curve[1][1] - |
|
|
|
|
|
|
|
6 * bt * t0 * curve[1][1] - |
|
|
|
|
|
|
|
3 * t0_2 * curve[2][1] + |
|
|
|
|
|
|
|
6 * bt * t0 * curve[2][1] + |
|
|
|
|
|
|
|
3 * t0_2 * curve[3][1]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Line derivatives
|
|
|
|
|
|
|
|
const dfx_ds = -(lineSegment[1][0] - lineSegment[0][0]); |
|
|
|
|
|
|
|
const dfy_ds = -(lineSegment[1][1] - lineSegment[0][1]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Jacobian determinant
|
|
|
|
|
|
|
|
const det = dfx_dt * dfy_ds - dfx_ds * dfy_dt; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (Math.abs(det) < 1e-12) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
t0 = t0 + h[0][0]; |
|
|
|
// Newton step
|
|
|
|
s0 = s0 + h[1][0]; |
|
|
|
const invDet = 1 / det; |
|
|
|
|
|
|
|
const dt = invDet * (dfy_ds * -fx - dfx_ds * -fy); |
|
|
|
|
|
|
|
const ds = invDet * (-dfy_dt * -fx + dfx_dt * -fy); |
|
|
|
|
|
|
|
|
|
|
|
const [tErr, sErr] = f(t0, s0); |
|
|
|
t0 += dt; |
|
|
|
error = Math.max(Math.abs(tErr), Math.abs(sErr)); |
|
|
|
s0 += ds; |
|
|
|
iter += 1; |
|
|
|
iter += 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -96,38 +127,18 @@ export const bezierEquation = <Point extends GlobalPoint | LocalPoint>( |
|
|
|
t ** 3 * c[3][1], |
|
|
|
t ** 3 * c[3][1], |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
const initial_guesses: [number, number][] = [ |
|
|
|
* Computes the intersection between a cubic spline and a line segment. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
export function curveIntersectLineSegment< |
|
|
|
|
|
|
|
Point extends GlobalPoint | LocalPoint, |
|
|
|
|
|
|
|
>(c: Curve<Point>, l: LineSegment<Point>): Point[] { |
|
|
|
|
|
|
|
const line = (s: number) => |
|
|
|
|
|
|
|
pointFrom<Point>( |
|
|
|
|
|
|
|
l[0][0] + s * (l[1][0] - l[0][0]), |
|
|
|
|
|
|
|
l[0][1] + s * (l[1][1] - l[0][1]), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const initial_guesses: [number, number][] = [ |
|
|
|
|
|
|
|
[0.5, 0], |
|
|
|
[0.5, 0], |
|
|
|
[0.2, 0], |
|
|
|
[0.2, 0], |
|
|
|
[0.8, 0], |
|
|
|
[0.8, 0], |
|
|
|
]; |
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
const calculate = ([t0, s0]: [number, number]) => { |
|
|
|
const calculate = <Point extends GlobalPoint | LocalPoint>( |
|
|
|
const solution = solve( |
|
|
|
[t0, s0]: [number, number], |
|
|
|
(t: number, s: number) => { |
|
|
|
l: LineSegment<Point>, |
|
|
|
const bezier_point = bezierEquation(c, t); |
|
|
|
c: Curve<Point>, |
|
|
|
const line_point = line(s); |
|
|
|
) => { |
|
|
|
|
|
|
|
const solution = solveWithAnalyticalJacobian(c, l, t0, s0, 1e-2, 3); |
|
|
|
return [ |
|
|
|
|
|
|
|
bezier_point[0] - line_point[0], |
|
|
|
|
|
|
|
bezier_point[1] - line_point[1], |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
t0, |
|
|
|
|
|
|
|
s0, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!solution) { |
|
|
|
if (!solution) { |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
@ -140,19 +151,25 @@ export function curveIntersectLineSegment< |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return bezierEquation(c, t); |
|
|
|
return bezierEquation(c, t); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let solution = calculate(initial_guesses[0]); |
|
|
|
/** |
|
|
|
|
|
|
|
* Computes the intersection between a cubic spline and a line segment. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
export function curveIntersectLineSegment< |
|
|
|
|
|
|
|
Point extends GlobalPoint | LocalPoint, |
|
|
|
|
|
|
|
>(c: Curve<Point>, l: LineSegment<Point>): Point[] { |
|
|
|
|
|
|
|
let solution = calculate(initial_guesses[0], l, c); |
|
|
|
if (solution) { |
|
|
|
if (solution) { |
|
|
|
return [solution]; |
|
|
|
return [solution]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
solution = calculate(initial_guesses[1]); |
|
|
|
solution = calculate(initial_guesses[1], l, c); |
|
|
|
if (solution) { |
|
|
|
if (solution) { |
|
|
|
return [solution]; |
|
|
|
return [solution]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
solution = calculate(initial_guesses[2]); |
|
|
|
solution = calculate(initial_guesses[2], l, c); |
|
|
|
if (solution) { |
|
|
|
if (solution) { |
|
|
|
return [solution]; |
|
|
|
return [solution]; |
|
|
|
} |
|
|
|
} |
|
|
|
|