115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
|
|
package geo
|
||
|
|
|
||
|
|
import (
|
||
|
|
"math"
|
||
|
|
"sort"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Ellipse struct {
|
||
|
|
Center *Point
|
||
|
|
Rx float64
|
||
|
|
Ry float64
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewEllipse(center *Point, rx, ry float64) *Ellipse {
|
||
|
|
return &Ellipse{
|
||
|
|
Center: center,
|
||
|
|
Rx: rx,
|
||
|
|
Ry: ry,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (e Ellipse) Intersections(segment Segment) []*Point {
|
||
|
|
// we check for intersections between the ellipse and line segment in the following manner:
|
||
|
|
// 0. we compute ignoring the ellipse's position, as if it were centered at 0,0 for a simpler computation
|
||
|
|
// 1. translate the line segment variables to match the ellipse's "translation" to 0,0
|
||
|
|
// 2. get the (infinite) line equation for the given line segment
|
||
|
|
// 3. compute the intersections between the line and ellipse
|
||
|
|
// 4. filter out intersections that are on the line but not on the line segment
|
||
|
|
// 5. apply the inverse translation to the intersection to get the result relative to where the ellipse and line actually are
|
||
|
|
intersections := []*Point{}
|
||
|
|
|
||
|
|
// ellipse equation: (x-cx)^2/rx^2 + (y-cy)^2/ry^2 = 1
|
||
|
|
// in the form: x^2/a^2 + y^2/b^2 = 1
|
||
|
|
a := e.Rx
|
||
|
|
b := e.Ry
|
||
|
|
a2 := a * a
|
||
|
|
b2 := b * b
|
||
|
|
if a <= 0 || b <= 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// line for a line segment between (x1,y1) and (x2, y2): (see https://en.wikipedia.org/wiki/Linear_equation#Two-point_form)
|
||
|
|
// y - y1 = ((y2 - y1)/(x2 - x1))(x - x1)
|
||
|
|
x1 := segment.Start.X - e.Center.X
|
||
|
|
y1 := segment.Start.Y - e.Center.Y
|
||
|
|
x2 := segment.End.X - e.Center.X
|
||
|
|
y2 := segment.End.Y - e.Center.Y
|
||
|
|
|
||
|
|
// Handle the edge case of a vertical line (avoiding dividing by 0)
|
||
|
|
if x1 == x2 {
|
||
|
|
// Ellipse solutions for a given x (from wolfram "(x^2)/(a^2)+(y^2)/(b^2)=1"):
|
||
|
|
// 1. y = +b*root(a^2 - x^2)/a
|
||
|
|
intersection1 := NewPoint(x1, +b*math.Sqrt(a2-x1*x1)/a)
|
||
|
|
// 2. y = -b*root(a^2 - x^2)/a
|
||
|
|
intersection2 := intersection1.Copy()
|
||
|
|
intersection2.Y *= -1
|
||
|
|
|
||
|
|
isPointOnLine := func(p *Point) bool {
|
||
|
|
ps := []float64{p.Y, y1, y2}
|
||
|
|
sort.Slice(ps, func(i, j int) bool {
|
||
|
|
return ps[i] < ps[j]
|
||
|
|
})
|
||
|
|
return ps[1] == p.Y
|
||
|
|
}
|
||
|
|
|
||
|
|
if isPointOnLine(intersection1) {
|
||
|
|
intersections = append(intersections, intersection1)
|
||
|
|
}
|
||
|
|
// when y = 0, intersection2 will be a duplicate of intersection1
|
||
|
|
if intersection2.Y != 0.0 && isPointOnLine(intersection2) {
|
||
|
|
intersections = append(intersections, intersection2)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, p := range intersections {
|
||
|
|
p.X += e.Center.X
|
||
|
|
p.Y += e.Center.Y
|
||
|
|
}
|
||
|
|
return intersections
|
||
|
|
}
|
||
|
|
|
||
|
|
// converting line to form: y = mx + c
|
||
|
|
// from: y - y1 = ((y2-y1)/(x2-x1))(x - x1)
|
||
|
|
// m = (y2-y1)/(x2-x1), c = y1 - m*x1
|
||
|
|
m := (y2 - y1) / (x2 - x1)
|
||
|
|
c := y1 - m*x1
|
||
|
|
isPointOnLine := func(p *Point) bool {
|
||
|
|
return PrecisionCompare(p.DistanceToLine(NewPoint(x1, y1), NewPoint(x2, y2)), 0, PRECISION) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// "(x^2)/(a^2)+(y^2)/(b^2) =1, y = mx + c" solutions from wolfram:
|
||
|
|
// if (a^2)(m^2) + b^2 != 0, and ab != 0
|
||
|
|
// 2 solutions 1 with +, 1 with -
|
||
|
|
// x = ( -c m a^2 {+/-} root(a^2 b^2 (a^2 m^2 + b^2 - c^2)) ) / (a^2 m^2 + b^2)
|
||
|
|
// y = ( c b^2 {+/-} m root(a^2 b^2 (a^2 m^2 + b^2 - c^2)) ) / (a^2 m^2 + b^2)
|
||
|
|
denom := a2*m*m + b2
|
||
|
|
// Note: we already checked a and b != 0 so denom == 0 is impossible (assuming no imaginary numbers)
|
||
|
|
root := math.Sqrt(a2 * b2 * (denom - c*c))
|
||
|
|
|
||
|
|
intersection1 := NewPoint((-m*c*a2+root)/denom, (c*b2+m*root)/denom)
|
||
|
|
intersection2 := NewPoint((-m*c*a2-root)/denom, (c*b2-m*root)/denom)
|
||
|
|
|
||
|
|
if isPointOnLine(intersection1) {
|
||
|
|
intersections = append(intersections, intersection1)
|
||
|
|
}
|
||
|
|
if !intersection1.Equals(intersection2) && isPointOnLine(intersection2) {
|
||
|
|
intersections = append(intersections, intersection2)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, p := range intersections {
|
||
|
|
p.X += e.Center.X
|
||
|
|
p.Y += e.Center.Y
|
||
|
|
}
|
||
|
|
return intersections
|
||
|
|
}
|