254 lines
5.3 KiB
Go
254 lines
5.3 KiB
Go
// bezier.go is a manual translation of the code from (including some amendments in the comments section)
|
|
// https://www.particleincell.com/2013/cubic-line-intersection/
|
|
|
|
package geo
|
|
|
|
import (
|
|
"math"
|
|
|
|
"gonum.org/v1/plot/font"
|
|
"gonum.org/v1/plot/tools/bezier"
|
|
"gonum.org/v1/plot/vg"
|
|
)
|
|
|
|
// How precise should comparisons be, avoid being too precise due to floating point issues
|
|
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))
|
|
}
|
|
|
|
// nolint
|
|
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
|
|
}
|
|
|
|
// nolint
|
|
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
|
|
|
|
//lint:ignore SA4008 TODO this returns before looping?
|
|
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)
|
|
|
|
//lint:ignore SA4004 TODO this always returns on the first iteration
|
|
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
|
|
}
|
|
|
|
// nolint
|
|
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
|
|
}
|
|
|
|
// nolint
|
|
func sgn(x float64) float64 {
|
|
if x < 0.0 {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// nolint
|
|
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
|
|
}
|