Added circle intersection points
This commit is contained in:
parent
4795a92b41
commit
f3377bb16f
91
geometry.go
91
geometry.go
|
@ -707,6 +707,12 @@ func (c Circle) Contains(u Vec) bool {
|
|||
return c.Radius >= toCenter.Len()
|
||||
}
|
||||
|
||||
// Formula returns the values of h and k, for the equation of the circle: (x-h)^2 + (y-k)^2 = r^2
|
||||
// where r is the radius of the circle.
|
||||
func (c Circle) Formula() (h, k float64) {
|
||||
return c.Center.X, c.Center.Y
|
||||
}
|
||||
|
||||
// maxCircle will return the larger circle based on the radius.
|
||||
func maxCircle(c, d Circle) Circle {
|
||||
if c.Radius < d.Radius {
|
||||
|
@ -870,7 +876,90 @@ func (c Circle) IntersectRect(r Rect) Vec {
|
|||
// IntersectionPoints returns all the points where the Circle intersects with the line provided. This can be zero, one or
|
||||
// two points, depending on the location of the shapes.
|
||||
func (c Circle) IntersectionPoints(l Line) []Vec {
|
||||
return []Vec{}
|
||||
cContainsA := c.Contains(l.A)
|
||||
cContainsB := c.Contains(l.B)
|
||||
|
||||
// Special case for both endpoint being contained within the circle
|
||||
if cContainsA && cContainsB {
|
||||
return []Vec{}
|
||||
}
|
||||
|
||||
// Get closest point on the line to this circles' center
|
||||
closestToCenter := l.Closest(c.Center)
|
||||
|
||||
// If the distance to the closest point is greater than the radius, there are no points of intersection
|
||||
if closestToCenter.To(c.Center).Len() > c.Radius {
|
||||
return []Vec{}
|
||||
}
|
||||
|
||||
// If the distance to the closest point is equal to the radius, the line is tangent and the closest point is the
|
||||
// point at which it touches the circle.
|
||||
if closestToCenter.To(c.Center).Len() == c.Radius {
|
||||
return []Vec{closestToCenter}
|
||||
}
|
||||
|
||||
// Special case for endpoint being on the circles' center
|
||||
if c.Center == l.A || c.Center == l.B {
|
||||
otherEnd := l.B
|
||||
if c.Center == l.B {
|
||||
otherEnd = l.A
|
||||
}
|
||||
intersect := c.Center.Add(c.Center.To(otherEnd).Unit().Scaled(c.Radius))
|
||||
return []Vec{intersect}
|
||||
}
|
||||
|
||||
// This means the distance to the closest point is less than the radius, so there is at least one intersection,
|
||||
// possibly two.
|
||||
|
||||
// If one of the end points exists within the circle, there is only one intersection
|
||||
if cContainsA || cContainsB {
|
||||
containedPoint := l.A
|
||||
otherEnd := l.B
|
||||
if cContainsB {
|
||||
containedPoint = l.B
|
||||
otherEnd = l.A
|
||||
}
|
||||
|
||||
// Use trigonometry to get the length of the line between the contained point and the intersection point.
|
||||
// The following is used to describe the triangle formed:
|
||||
// - a is the side between contained point and circle center
|
||||
// - b is the side between the center and the intersection point (radius)
|
||||
// - c is the side between the contained point and the intersection point
|
||||
// The captials of these letters are used as the angles opposite the respective sides.
|
||||
// a and b are known
|
||||
a := containedPoint.To(c.Center).Len()
|
||||
b := c.Radius
|
||||
// B can be calculated by subtracting the angle of b (to the x-axis) from the angle of c (to the x-axis)
|
||||
B := containedPoint.To(c.Center).Angle() - containedPoint.To(otherEnd).Angle()
|
||||
// Using the Sin rule we can get A
|
||||
A := math.Asin((a * math.Sin(B)) / b)
|
||||
// Using the rule that there are 180 degrees (or Pi radians) in a triangle, we can now get C
|
||||
C := math.Pi - A + B
|
||||
// If C is zero, the line segment is in-line with the center-intersect line.
|
||||
var c float64
|
||||
if C == 0 {
|
||||
c = b - a
|
||||
} else {
|
||||
// Using the Sine rule again, we can now get c
|
||||
c = (a * math.Sin(C)) / math.Sin(A)
|
||||
}
|
||||
// Travelling from the contained point to the other end by length of a will provide the intersection point.
|
||||
return []Vec{
|
||||
containedPoint.Add(containedPoint.To(otherEnd).Unit().Scaled(c)),
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise the endpoints exist outside of the circle, and the line segment intersects in two locations.
|
||||
// The vector formed by going from the closest point to the center of the circle will be perpendicular to the line;
|
||||
// this forms a right-angled triangle with the intersection points, with the radius as the hypotenuse.
|
||||
// Calculate the other triangles' sides' length.
|
||||
a := math.Sqrt(math.Pow(c.Radius, 2) - math.Pow(closestToCenter.To(c.Center).Len(), 2))
|
||||
|
||||
// Travelling in both directions from the closest point by length of a will provide the two intersection points.
|
||||
first := closestToCenter.Add(closestToCenter.To(l.A).Unit().Scaled(a))
|
||||
second := closestToCenter.Add(closestToCenter.To(l.B).Unit().Scaled(a))
|
||||
|
||||
return []Vec{first, second}
|
||||
}
|
||||
|
||||
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
|
||||
|
|
|
@ -10,6 +10,19 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them.
|
||||
// Effectively this compares two floats to a given decimal point.
|
||||
// Example:
|
||||
// closeEnough(100.125342432, 100.125, 2) == true
|
||||
// closeEnough(math.Pi, 3.14, 2) == true
|
||||
// closeEnough(0.1234, 0.1245, 3) == false
|
||||
func closeEnough(got, expected float64, decimalAccuracy int) bool {
|
||||
gotShifted := got * math.Pow10(decimalAccuracy)
|
||||
expectedShifted := expected * math.Pow10(decimalAccuracy)
|
||||
|
||||
return math.Trunc(gotShifted) == math.Trunc(expectedShifted)
|
||||
}
|
||||
|
||||
func TestRect_Edges(t *testing.T) {
|
||||
type fields struct {
|
||||
Min pixel.Vec
|
||||
|
@ -623,7 +636,54 @@ func TestCircle_IntersectPoints(t *testing.T) {
|
|||
args args
|
||||
want []pixel.Vec
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
{
|
||||
name: "Line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(2, 2), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{pixel.V(1.292, 1.292), pixel.V(2.707, 2.707)},
|
||||
},
|
||||
{
|
||||
name: "Line intersects circle at one point",
|
||||
fields: fields{Center: pixel.V(-0.5, -0.5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{pixel.V(0.207, 0.207)},
|
||||
},
|
||||
{
|
||||
name: "Line endpoint is circle center",
|
||||
fields: fields{Center: pixel.V(0, 0), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{pixel.V(0.707, 0.707)},
|
||||
},
|
||||
{
|
||||
name: "Both line endpoints within circle",
|
||||
fields: fields{Center: pixel.V(0, 0), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0.2, 0.2), pixel.V(0.5, 0.5))},
|
||||
want: []pixel.Vec{},
|
||||
},
|
||||
{
|
||||
name: "Line does not intersect circle",
|
||||
fields: fields{Center: pixel.V(10, 0), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{},
|
||||
},
|
||||
{
|
||||
name: "Horizontal line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 5), pixel.V(10, 5))},
|
||||
want: []pixel.Vec{pixel.V(4, 5), pixel.V(6, 5)},
|
||||
},
|
||||
{
|
||||
name: "Vertical line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(5, 0), pixel.V(5, 10))},
|
||||
want: []pixel.Vec{pixel.V(5, 4), pixel.V(5, 6)},
|
||||
},
|
||||
{
|
||||
name: "Left and down line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(10, 10), pixel.V(0, 0))},
|
||||
want: []pixel.Vec{pixel.V(5.707, 5.707), pixel.V(4.292, 4.292)},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -631,27 +691,17 @@ func TestCircle_IntersectPoints(t *testing.T) {
|
|||
Center: tt.fields.Center,
|
||||
Radius: tt.fields.Radius,
|
||||
}
|
||||
if got := c.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.IntersectPoints() = %v, want %v", got, tt.want)
|
||||
got := c.IntersectionPoints(tt.args.l)
|
||||
for i, v := range got {
|
||||
if !closeEnough(v.X, tt.want[i].X, 2) || !closeEnough(v.Y, tt.want[i].Y, 2) {
|
||||
t.Errorf("Circle.IntersectPoints() = %v, want %v", v, tt.want[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRect_IntersectCircle(t *testing.T) {
|
||||
// closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them.
|
||||
// Effectively this compares two floats to a given decimal point.
|
||||
// Example:
|
||||
// closeEnough(100.125342432, 100.125, 2) == true
|
||||
// closeEnough(math.Pi, 3.14, 2) == true
|
||||
// closeEnough(0.1234, 0.1245, 3) == false
|
||||
closeEnough := func(got, expected float64, decimalAccuracy int) bool {
|
||||
gotShifted := got * math.Pow10(decimalAccuracy)
|
||||
expectedShifted := expected * math.Pow10(decimalAccuracy)
|
||||
|
||||
return math.Trunc(gotShifted) == math.Trunc(expectedShifted)
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
Min pixel.Vec
|
||||
Max pixel.Vec
|
||||
|
|
Loading…
Reference in New Issue