From 680d16c17ba4f2d52d303b641fc5cbde63c06874 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 18 Feb 2019 12:03:54 +0000 Subject: [PATCH 1/2] Corrected returned Vectors for corner overlaps --- geometry.go | 65 +++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/geometry.go b/geometry.go index 465cc2f..1e2922d 100644 --- a/geometry.go +++ b/geometry.go @@ -484,9 +484,6 @@ func (c Circle) Intersect(d Circle) Circle { // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully 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 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 @@ -498,8 +495,8 @@ func (c Circle) IntersectRect(r Rect) Vec { // 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) + 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 @@ -507,48 +504,52 @@ func (c Circle) IntersectRect(r Rect) Vec { if rToC.Y < 0 { 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 { // 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 { // 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))) + centerToCorner = c.Center.To(r.Min) } 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)) + centerToCorner = c.Center.To(r.Max) } - 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 - 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)) + centerToCorner = c.Center.To(rectTopLeft) } - 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 - 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))) + centerToCorner = c.Center.To(rectBottomRight) } - } - // No intersect - if h == 0 && v == 0 { - return ZV - } + cornerToCircumferenceLen := c.Radius - centerToCorner.Len() - if math.Abs(h) > math.Abs(v) { - // Vertical distance shorter - return V(0, v) + return centerToCorner.Unit().Scaled(cornerToCircumferenceLen) } - return V(h, 0) } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such From b3246f6234b4c020ad188b09649c7c5e98731d97 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 18 Feb 2019 12:16:16 +0000 Subject: [PATCH 2/2] Updated tests --- geometry_test.go | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 57d8b4d..dfa78cf 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -542,6 +542,19 @@ func TestCircle_Intersect(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 { Min pixel.Vec Max pixel.Vec @@ -576,26 +589,26 @@ func TestRect_IntersectCircle(t *testing.T) { { name: "Rect.IntersectCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(0, 0), 1)}, - want: pixel.V(1, 0), + args: args{c: pixel.C(pixel.V(-0.5, -0.5), 1)}, + want: pixel.V(-0.2, -0.2), }, { name: "Rect.IntersectCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(0, 10), 1)}, - want: pixel.V(1, 0), + args: args{c: pixel.C(pixel.V(-0.5, 10.5), 1)}, + want: pixel.V(-0.2, 0.2), }, { name: "Rect.IntersectCircle(): 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), + args: args{c: pixel.C(pixel.V(10.5, -0.5), 1)}, + want: pixel.V(0.2, -0.2), }, { name: "Rect.IntersectCircle(): 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), + args: args{c: pixel.C(pixel.V(10.5, 10.5), 1)}, + want: pixel.V(0.2, 0.2), }, { name: "Rect.IntersectCircle(): circle overlaps two corners", @@ -670,7 +683,8 @@ func TestRect_IntersectCircle(t *testing.T) { Min: tt.fields.Min, 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) } })