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()
|
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.
|
// maxCircle will return the larger circle based on the radius.
|
||||||
func maxCircle(c, d Circle) Circle {
|
func maxCircle(c, d Circle) Circle {
|
||||||
if c.Radius < d.Radius {
|
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
|
// 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.
|
// two points, depending on the location of the shapes.
|
||||||
func (c Circle) IntersectionPoints(l Line) []Vec {
|
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
|
// 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"
|
"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) {
|
func TestRect_Edges(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
Min pixel.Vec
|
Min pixel.Vec
|
||||||
|
@ -623,7 +636,54 @@ func TestCircle_IntersectPoints(t *testing.T) {
|
||||||
args args
|
args args
|
||||||
want []pixel.Vec
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -631,27 +691,17 @@ func TestCircle_IntersectPoints(t *testing.T) {
|
||||||
Center: tt.fields.Center,
|
Center: tt.fields.Center,
|
||||||
Radius: tt.fields.Radius,
|
Radius: tt.fields.Radius,
|
||||||
}
|
}
|
||||||
if got := c.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) {
|
got := c.IntersectionPoints(tt.args.l)
|
||||||
t.Errorf("Circle.IntersectPoints() = %v, want %v", got, tt.want)
|
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) {
|
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 {
|
type fields struct {
|
||||||
Min pixel.Vec
|
Min pixel.Vec
|
||||||
Max pixel.Vec
|
Max pixel.Vec
|
||||||
|
|
Loading…
Reference in New Issue