@ -21,20 +21,9 @@ export function curve<Point extends GlobalPoint | LocalPoint>(
@@ -21,20 +21,9 @@ export function curve<Point extends GlobalPoint | LocalPoint>(
return [ a , b , c , d ] as Curve < Point > ;
}
function gradient (
f : ( t : number , s : number ) = > number ,
t0 : number ,
s0 : number ,
delta : number = 1 e - 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 ] ,
function solveWithAnalyticalJacobian < Point extends GlobalPoint | LocalPoint > (
curve : Curve < Point > ,
lineSegment : LineSegment < Point > ,
t0 : number ,
s0 : number ,
tolerance : number = 1 e - 3 ,
@ -48,33 +37,75 @@ function solve(
@@ -48,33 +37,75 @@ function solve(
return null ;
}
const y0 = f ( t0 , s0 ) ;
const jacobian = [
gradient ( ( t , s ) = > f ( t , s ) [ 0 ] , t0 , s0 ) ,
gradient ( ( t , s ) = > f ( t , s ) [ 1 ] , t0 , s0 ) ,
] ;
const b = [ [ - y0 [ 0 ] ] , [ - y0 [ 1 ] ] ] ;
const det =
jacobian [ 0 ] [ 0 ] * jacobian [ 1 ] [ 1 ] - jacobian [ 0 ] [ 1 ] * jacobian [ 1 ] [ 0 ] ;
// Compute bezier point at parameter t0
const bt = 1 - t0 ;
const bt2 = bt * bt ;
const bt3 = bt2 * bt ;
const t0_2 = t0 * t0 ;
const t0_3 = t0_2 * t0 ;
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 ) {
return null ;
if ( error < tolerance ) {
b reak ;
}
const iJ = [
[ jacobian [ 1 ] [ 1 ] / det , - jacobian [ 0 ] [ 1 ] / det ] ,
[ - jacobian [ 1 ] [ 0 ] / det , jacobian [ 0 ] [ 0 ] / det ] ,
] ;
const h = [
[ iJ [ 0 ] [ 0 ] * b [ 0 ] [ 0 ] + iJ [ 0 ] [ 1 ] * b [ 1 ] [ 0 ] ] ,
[ iJ [ 1 ] [ 0 ] * b [ 0 ] [ 0 ] + iJ [ 1 ] [ 1 ] * b [ 1 ] [ 0 ] ] ,
] ;
// Analytical derivatives
const dfx_dt =
- 3 * bt2 * curve [ 0 ] [ 0 ] +
3 * bt2 * curve [ 1 ] [ 0 ] -
6 * bt * t0 * curve [ 1 ] [ 0 ] -
3 * t0_2 * curve [ 2 ] [ 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 ) < 1 e - 12 ) {
return null ;
}
t0 = t0 + h [ 0 ] [ 0 ] ;
s0 = s0 + h [ 1 ] [ 0 ] ;
// Newton step
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 ) ;
error = Math . max ( Math . abs ( tErr ) , Math . abs ( sErr ) ) ;
t0 += dt ;
s0 += ds ;
iter += 1 ;
}
@ -96,38 +127,18 @@ export const bezierEquation = <Point extends GlobalPoint | LocalPoint>(
@@ -96,38 +127,18 @@ export const bezierEquation = <Point extends GlobalPoint | LocalPoint>(
t * * 3 * c [ 3 ] [ 1 ] ,
) ;
/ * *
* 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 ] [ ] = [
const initial_guesses : [ number , number ] [ ] = [
[ 0.5 , 0 ] ,
[ 0.2 , 0 ] ,
[ 0.8 , 0 ] ,
] ;
const calculate = ( [ t0 , s0 ] : [ number , number ] ) = > {
const solution = solve (
( t : number , s : number ) = > {
const bezier_point = bezierEquation ( c , t ) ;
const line_point = line ( s ) ;
return [
bezier_point [ 0 ] - line_point [ 0 ] ,
bezier_point [ 1 ] - line_point [ 1 ] ,
] ;
} ,
t0 ,
s0 ,
) ;
] ;
const calculate = < Point extends GlobalPoint | LocalPoint > (
[ t0 , s0 ] : [ number , number ] ,
l : LineSegment < Point > ,
c : Curve < Point > ,
) = > {
const solution = solveWithAnalyticalJacobian ( c , l , t0 , s0 , 1 e - 2 , 3 ) ;
if ( ! solution ) {
return null ;
@ -140,19 +151,25 @@ export function curveIntersectLineSegment<
@@ -140,19 +151,25 @@ export function curveIntersectLineSegment<
}
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 ) {
return [ solution ] ;
}
solution = calculate ( initial_guesses [ 1 ] ) ;
solution = calculate ( initial_guesses [ 1 ] , l , c ) ;
if ( solution ) {
return [ solution ] ;
}
solution = calculate ( initial_guesses [ 2 ] ) ;
solution = calculate ( initial_guesses [ 2 ] , l , c ) ;
if ( solution ) {
return [ solution ] ;
}