Merge pull request #162 from bcvery1/fixCircleIntersect
Fix circle intersect
This commit is contained in:
commit
3cc0ec3d30
65
geometry.go
65
geometry.go
|
@ -484,9 +484,6 @@ func (c Circle) Intersect(d Circle) Circle {
|
||||||
// - The Rect contains the Circle, partially or fully
|
// - The Rect contains the Circle, partially or fully
|
||||||
// - The Circle contains the Rect, partially of fully
|
// - The Circle contains the Rect, partially of fully
|
||||||
func (c Circle) IntersectRect(r Rect) Vec {
|
func (c Circle) IntersectRect(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
|
// 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) {
|
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
|
// 'grow' the Rect by c.Radius in each orthagonal
|
||||||
|
@ -498,8 +495,8 @@ func (c Circle) IntersectRect(r Rect) Vec {
|
||||||
|
|
||||||
// Get minimum distance to travel out of Rect
|
// Get minimum distance to travel out of Rect
|
||||||
rToC := r.Center().To(c.Center)
|
rToC := r.Center().To(c.Center)
|
||||||
h = c.Radius - math.Abs(rToC.X) + (r.W() / 2)
|
h := c.Radius - math.Abs(rToC.X) + (r.W() / 2)
|
||||||
v = c.Radius - math.Abs(rToC.Y) + (r.H() / 2)
|
v := c.Radius - math.Abs(rToC.Y) + (r.H() / 2)
|
||||||
|
|
||||||
if rToC.X < 0 {
|
if rToC.X < 0 {
|
||||||
h = -h
|
h = -h
|
||||||
|
@ -507,48 +504,52 @@ func (c Circle) IntersectRect(r Rect) Vec {
|
||||||
if rToC.Y < 0 {
|
if rToC.Y < 0 {
|
||||||
v = -v
|
v = -v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
} else {
|
} else {
|
||||||
// The center is in the diagonal quadrants
|
// The center is in the diagonal quadrants
|
||||||
|
|
||||||
|
// Helper points to make code below easy to read.
|
||||||
|
rectTopLeft := V(r.Min.X, r.Max.Y)
|
||||||
|
rectBottomRight := V(r.Max.X, r.Min.Y)
|
||||||
|
|
||||||
|
// Check for overlap.
|
||||||
|
if !(c.Contains(r.Min) || c.Contains(r.Max) || c.Contains(rectTopLeft) || c.Contains(rectBottomRight)) {
|
||||||
|
// No overlap.
|
||||||
|
return ZV
|
||||||
|
}
|
||||||
|
|
||||||
|
var centerToCorner Vec
|
||||||
if c.Center.To(r.Min).Len() <= c.Radius {
|
if c.Center.To(r.Min).Len() <= c.Radius {
|
||||||
// Closest to bottom-left
|
// Closest to bottom-left
|
||||||
cornerToCenter := r.Min.To(c.Center)
|
centerToCorner = c.Center.To(r.Min)
|
||||||
// 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 {
|
if c.Center.To(r.Max).Len() <= c.Radius {
|
||||||
// Closest to top-right
|
// Closest to top-right
|
||||||
cornerToCenter := r.Max.To(c.Center)
|
centerToCorner = c.Center.To(r.Max)
|
||||||
// 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 {
|
if c.Center.To(rectTopLeft).Len() <= c.Radius {
|
||||||
// Closest to top-left
|
// Closest to top-left
|
||||||
cornerToCenter := V(r.Min.X, r.Max.Y).To(c.Center)
|
centerToCorner = c.Center.To(rectTopLeft)
|
||||||
// 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 {
|
if c.Center.To(rectBottomRight).Len() <= c.Radius {
|
||||||
// Closest to bottom-right
|
// Closest to bottom-right
|
||||||
cornerToCenter := V(r.Max.X, r.Min.Y).To(c.Center)
|
centerToCorner = c.Center.To(rectBottomRight)
|
||||||
// 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)))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No intersect
|
cornerToCircumferenceLen := c.Radius - centerToCorner.Len()
|
||||||
if h == 0 && v == 0 {
|
|
||||||
return ZV
|
|
||||||
}
|
|
||||||
|
|
||||||
if math.Abs(h) > math.Abs(v) {
|
return centerToCorner.Unit().Scaled(cornerToCircumferenceLen)
|
||||||
// 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
|
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
|
||||||
|
|
|
@ -542,6 +542,19 @@ func TestCircle_Intersect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
@ -576,26 +589,26 @@ func TestRect_IntersectCircle(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Rect.IntersectCircle(): circle overlaps bottom-left corner",
|
name: "Rect.IntersectCircle(): circle overlaps bottom-left corner",
|
||||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||||
args: args{c: pixel.C(pixel.V(0, 0), 1)},
|
args: args{c: pixel.C(pixel.V(-0.5, -0.5), 1)},
|
||||||
want: pixel.V(1, 0),
|
want: pixel.V(-0.2, -0.2),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Rect.IntersectCircle(): circle overlaps top-left corner",
|
name: "Rect.IntersectCircle(): circle overlaps top-left corner",
|
||||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||||
args: args{c: pixel.C(pixel.V(0, 10), 1)},
|
args: args{c: pixel.C(pixel.V(-0.5, 10.5), 1)},
|
||||||
want: pixel.V(1, 0),
|
want: pixel.V(-0.2, 0.2),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Rect.IntersectCircle(): circle overlaps bottom-right corner",
|
name: "Rect.IntersectCircle(): circle overlaps bottom-right corner",
|
||||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||||
args: args{c: pixel.C(pixel.V(10, 0), 1)},
|
args: args{c: pixel.C(pixel.V(10.5, -0.5), 1)},
|
||||||
want: pixel.V(-1, 0),
|
want: pixel.V(0.2, -0.2),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Rect.IntersectCircle(): circle overlaps top-right corner",
|
name: "Rect.IntersectCircle(): circle overlaps top-right corner",
|
||||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||||
args: args{c: pixel.C(pixel.V(10, 10), 1)},
|
args: args{c: pixel.C(pixel.V(10.5, 10.5), 1)},
|
||||||
want: pixel.V(-1, 0),
|
want: pixel.V(0.2, 0.2),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Rect.IntersectCircle(): circle overlaps two corners",
|
name: "Rect.IntersectCircle(): circle overlaps two corners",
|
||||||
|
@ -670,7 +683,8 @@ func TestRect_IntersectCircle(t *testing.T) {
|
||||||
Min: tt.fields.Min,
|
Min: tt.fields.Min,
|
||||||
Max: tt.fields.Max,
|
Max: tt.fields.Max,
|
||||||
}
|
}
|
||||||
if got := r.IntersectCircle(tt.args.c); got != tt.want {
|
got := r.IntersectCircle(tt.args.c)
|
||||||
|
if !closeEnough(got.X, tt.want.X, 2) || !closeEnough(got.Y, tt.want.Y, 2) {
|
||||||
t.Errorf("Rect.IntersectCircle() = %v, want %v", got, tt.want)
|
t.Errorf("Rect.IntersectCircle() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue