This commit is contained in:
Ben Cragg 2019-01-31 08:22:59 +00:00
parent 0a0c8ff110
commit ab70533793
2 changed files with 112 additions and 26 deletions

View File

@ -318,14 +318,15 @@ func (r Rect) Intersect(s Rect) Rect {
return t
}
// IntersectsCircle returns whether the Circle and the Rect intersect.
// IntersectsCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
// the perimeters touch.
//
// This function will return true if:
// - The Rect contains the Circle, partially or fully
// - The Circle contains the Rect, partially of fully
// - An edge of the Rect is a tangent to the Circle
func (r Rect) IntersectsCircle(c Circle) bool {
return c.IntersectsRect(r)
func (r Rect) IntersectsCircle(c Circle) Vec {
return c.IntersectsRect(r).Scaled(-1)
}
// Circle is a 2D circle. It is defined by two properties:
@ -475,24 +476,79 @@ func (c Circle) Intersect(d Circle) Circle {
}
}
// IntersectsRect returns whether the Circle and the Rect intersect.
// IntersectsRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
// the perimeters touch.
//
// This function will return true if:
// - The Rect contains the Circle, partially or fully
// - The Circle contains the Rect, partially of fully
// - An edge of the Rect is a tangent to the Circle
func (c Circle) IntersectsRect(r Rect) bool {
func (c Circle) IntersectsRect(r Rect) Vec {
// h and v will hold the minimum horizontal and vertical distances (respectively) to avoid overlapping
var h, v float64
// Checks if the c.Center is not in the diagonal quadrants of the rectangle
if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) {
// 'grow' the Rect by c.Radius in each orthagonal
return Rect{
Min: r.Min.Sub(V(c.Radius, c.Radius)),
Max: r.Max.Add(V(c.Radius, c.Radius)),
}.Contains(c.Center)
grown := Rect{Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius))}
if !grown.Contains(c.Center) {
// c.Center not close enough to overlap, return zero-vector
return ZV
}
// Get minimum distance to travel out of Rect
rToC := r.Center().To(c.Center)
h = c.Radius - math.Abs(rToC.X) + (r.W() / 2)
v = c.Radius - math.Abs(rToC.Y) + (r.H() / 2)
if rToC.X < 0 {
h = -h
}
if rToC.Y < 0 {
v = -v
}
} else {
// The center is in the diagonal quadrants
if c.Center.To(r.Min).Len() <= c.Radius {
// Closest to bottom-left
cornerToCenter := r.Min.To(c.Center)
// Get the horizontal and vertical overlaps
h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))
v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)))
}
if c.Center.To(r.Max).Len() <= c.Radius {
// Closest to top-right
cornerToCenter := r.Max.To(c.Center)
// Get the horizontal and vertical overlaps
h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))
v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))
}
if c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius {
// Closest to top-left
cornerToCenter := V(r.Min.X, r.Max.Y).To(c.Center)
// Get the horizontal and vertical overlaps
h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)))
v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))
}
if c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius {
// Closest to bottom-right
cornerToCenter := V(r.Max.X, r.Min.Y).To(c.Center)
// Get the horizontal and vertical overlaps
h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)))
v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)))
}
}
// The center is in the diagonal quadrants
return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius ||
c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius || c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius
// No intersect
if h == 0 && v == 0 {
return ZV
}
if math.Abs(h) > math.Abs(v) {
// Vertical distance shorter
return V(0, v)
}
return V(h, 0)
}
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such

View File

@ -553,55 +553,85 @@ func TestRect_IntersectsCircle(t *testing.T) {
name string
fields fields
args args
want bool
want pixel.Vec
}{
{
name: "Rect.IntersectsCircle(): no overlap",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(50, 50), 1)},
want: false,
want: pixel.ZV,
},
{
name: "Rect.IntersectsCircle(): circle contains rect",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(5, 5), 10)},
want: true,
want: pixel.V(-15, 0),
},
{
name: "Rect.IntersectsCircle(): rect contains circle",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(5, 5), 1)},
want: true,
want: pixel.V(-6, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(-.5, -.5), 1)},
want: true,
args: args{c: pixel.C(pixel.V(0, 0), 1)},
want: pixel.V(1, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps top-left corner",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(-.5, 10.5), 1)},
want: true,
args: args{c: pixel.C(pixel.V(0, 10), 1)},
want: pixel.V(1, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps bottom-right corner",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(10, 0), 1)},
want: pixel.V(-1, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps top-right corner",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(10, 10), 1)},
want: pixel.V(-1, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps two corners",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(0, 5), 6)},
want: true,
want: pixel.V(6, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps one edge",
name: "Rect.IntersectsCircle(): circle overlaps left edge",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(0, 5), 1)},
want: true,
want: pixel.V(1, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps bottom edge",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(5, 0), 1)},
want: pixel.V(0, 1),
},
{
name: "Rect.IntersectsCircle(): circle overlaps right edge",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(10, 5), 1)},
want: pixel.V(-1, 0),
},
{
name: "Rect.IntersectsCircle(): circle overlaps top edge",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(5, 10), 1)},
want: pixel.V(0, -1),
},
{
name: "Rect.IntersectsCircle(): edge is tangent",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(-1, 5), 1)},
want: true,
want: pixel.ZV,
},
}
for _, tt := range tests {