package geo
import (
"math"
"gonum.org/v1/plot/font"
"gonum.org/v1/plot/tools/bezier"
"gonum.org/v1/plot/vg"
)
const PRECISION = 0.0001
type BezierCurve struct {
Curve *bezier .Curve
points []*Point
}
func NewBezierCurve (points []*Point ) *BezierCurve {
internalPoints := make ([]vg .Point , len (points ))
for i := 0 ; i < len (points ); i ++ {
internalPoints [i ] = vg .Point {
X : font .Length (points [i ].X ),
Y : font .Length (points [i ].Y ),
}
}
internalCurve := bezier .New (internalPoints ...)
curve := &BezierCurve {
Curve : &internalCurve ,
}
curve .points = points
return curve
}
func (bc BezierCurve ) Intersections (segment Segment ) []*Point {
return ComputeIntersections (
[]float64 {
bc .points [0 ].X ,
bc .points [1 ].X ,
bc .points [2 ].X ,
bc .points [3 ].X ,
},
[]float64 {
bc .points [0 ].Y ,
bc .points [1 ].Y ,
bc .points [2 ].Y ,
bc .points [3 ].Y ,
},
[]float64 {
segment .Start .X ,
segment .End .X ,
},
[]float64 {
segment .Start .Y ,
segment .End .Y ,
},
)
}
func (bc BezierCurve ) At (point float64 ) *Point {
curvePoint := bc .Curve .Point (point )
return NewPoint (float64 (curvePoint .X ), float64 (curvePoint .Y ))
}
func ComputeIntersections (px , py , lx , ly []float64 ) []*Point {
out := make ([]*Point , 0 )
var A = ly [1 ] - ly [0 ]
var B = lx [0 ] - lx [1 ]
var C = lx [0 ]*(ly [0 ]-ly [1 ]) + ly [0 ]*(lx [1 ]-lx [0 ])
var bx = bezierCoeffs (px [0 ], px [1 ], px [2 ], px [3 ])
var by = bezierCoeffs (py [0 ], py [1 ], py [2 ], py [3 ])
P := make ([]float64 , 4 )
P [0 ] = A *bx [0 ] + B *by [0 ]
P [1 ] = A *bx [1 ] + B *by [1 ]
P [2 ] = A *bx [2 ] + B *by [2 ]
P [3 ] = A *bx [3 ] + B *by [3 ] + C
var r = cubicRoots (P )
for i := 0 ; i < 3 ; i ++ {
t := r [i ]
point := &Point {
X : bx [0 ]*t *t *t + bx [1 ]*t *t + bx [2 ]*t + bx [3 ],
Y : by [0 ]*t *t *t + by [1 ]*t *t + by [2 ]*t + by [3 ],
}
var s float64
if (lx [1 ] - lx [0 ]) != 0 {
s = (point .X - lx [0 ]) / (lx [1 ] - lx [0 ])
} else {
s = (point .Y - ly [0 ]) / (ly [1 ] - ly [0 ])
}
tLT0 := PrecisionCompare (t , 0 , PRECISION ) < 0
tGT1 := PrecisionCompare (t , 1 , PRECISION ) > 0
sLT0 := PrecisionCompare (s , 0 , PRECISION ) < 0
sGT1 := PrecisionCompare (s , 1 , PRECISION ) > 0
if !(tLT0 || tGT1 || sLT0 || sGT1 ) {
out = append (out , point )
}
}
return out
}
func cubicRoots(P []float64 ) []float64 {
if PrecisionCompare (P [0 ], 0 , PRECISION ) == 0 {
if PrecisionCompare (P [1 ], 0 , PRECISION ) == 0 {
t := make ([]float64 , 3 )
t [0 ] = -1 * (P [3 ] / P [2 ])
t [1 ] = -1
t [2 ] = -1
for i := 0 ; i < 1 ; i ++ {
tiLT0 := PrecisionCompare (t [i ], 0 , PRECISION ) < 0
tiGT1 := PrecisionCompare (t [i ], 1 , PRECISION ) > 0
if tiLT0 || tiGT1 {
t [i ] = -1
}
}
t = sortSpecial (t )
return t
}
var DQ = math .Pow (P [2 ], 2 ) - 4 *P [1 ]*P [3 ]
if PrecisionCompare (DQ , 0 , PRECISION ) >= 0 {
DQ = math .Sqrt (DQ )
t := make ([]float64 , 3 )
t [0 ] = -1 * ((DQ + P [2 ]) / (2 * P [1 ]))
t [1 ] = ((DQ - P [2 ]) / (2 * P [1 ]))
t [2 ] = -1
for i := 0 ; i < 2 ; i ++ {
tiLT0 := PrecisionCompare (t [i ], 0 , PRECISION ) < 0
tiGT1 := PrecisionCompare (t [i ], 1 , PRECISION ) > 0
if tiLT0 || tiGT1 {
t [i ] = -1
}
t = sortSpecial (t )
return t
}
}
}
var a = P [0 ]
var b = P [1 ]
var c = P [2 ]
var d = P [3 ]
var A = b / a
var B = c / a
var C = d / a
var Q , R , D , Im float64
Q = (3 *B - math .Pow (A , 2 )) / 9
R = (9 *A *B - 27 *C - 2 *math .Pow (A , 3 )) / 54
D = math .Pow (Q , 3 ) + math .Pow (R , 2 )
t := make ([]float64 , 3 )
if PrecisionCompare (D , 0 , PRECISION ) >= 0 {
var S = sgn (R +math .Sqrt (D )) * math .Pow (math .Abs (R +math .Sqrt (D )), (1 /3.0 ))
var T = sgn (R -math .Sqrt (D )) * math .Pow (math .Abs (R -math .Sqrt (D )), (1 /3.0 ))
t [0 ] = -A /3 + (S + T )
t [1 ] = -A /3 - (S +T )/2
t [2 ] = -A /3 - (S +T )/2
Im = math .Abs (math .Sqrt (3 ) * (S - T ) / 2 )
if PrecisionCompare (Im , 0 , PRECISION ) != 0 {
t [1 ] = -1
t [2 ] = -1
}
} else {
var th = math .Acos (R / math .Sqrt (-math .Pow (Q , 3 )))
t [0 ] = 2 *math .Sqrt (-Q )*math .Cos (th /3 ) - A /3
t [1 ] = 2 *math .Sqrt (-Q )*math .Cos ((th +2 *math .Pi )/3 ) - A /3
t [2 ] = 2 *math .Sqrt (-Q )*math .Cos ((th +4 *math .Pi )/3 ) - A /3
Im = 0.0
}
for i := 0 ; i < 3 ; i ++ {
tiLT0 := PrecisionCompare (t [i ], 0 , PRECISION ) < 0
tiGT1 := PrecisionCompare (t [i ], 1 , PRECISION ) > 0
if tiLT0 || tiGT1 {
t [i ] = -1
}
}
t = sortSpecial (t )
return t
}
func sortSpecial(a []float64 ) []float64 {
var flip bool
var temp float64
for {
flip = false
for i := 0 ; i < len (a )-1 ; i ++ {
ai1GTEQ0 := PrecisionCompare (a [i +1 ], 0 , PRECISION ) >= 0
aiGTai1 := PrecisionCompare (a [i ], a [i +1 ], PRECISION ) > 0
aiLT0 := PrecisionCompare (a [i ], 0 , PRECISION ) < 0
if (ai1GTEQ0 && aiGTai1 ) || (aiLT0 && ai1GTEQ0 ) {
flip = true
temp = a [i ]
a [i ] = a [i +1 ]
a [i +1 ] = temp
}
}
if !flip {
break
}
}
return a
}
func sgn(x float64 ) float64 {
if x < 0.0 {
return -1
}
return 1
}
func bezierCoeffs(P0 , P1 , P2 , P3 float64 ) []float64 {
Z := make ([]float64 , 4 )
Z [0 ] = -P0 + 3 *P1 + -3 *P2 + P3
Z [1 ] = 3 *P0 - 6 *P1 + 3 *P2
Z [2 ] = -3 *P0 + 3 *P1
Z [3 ] = P0
return Z
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .