From fa425eb2c1f05cdb7c555cc83fd4b4c59c27f011 Mon Sep 17 00:00:00 2001 From: mrsombre <376535+mrsombre@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:23:04 +0100 Subject: [PATCH] More rectangle logic --- geometry_rect.go | 52 +++++++++++++ geometry_rect_test.go | 177 +++++++++++++++++++++++++++++++----------- 2 files changed, 183 insertions(+), 46 deletions(-) diff --git a/geometry_rect.go b/geometry_rect.go index 2ff84a7..f923c8e 100644 --- a/geometry_rect.go +++ b/geometry_rect.go @@ -38,6 +38,15 @@ func (r Rect) Center() Point { ) } +func (r Rect) Symmetric(width, height float64) Rect { + return Rect{ + Xf: width - r.Xf, + Xt: width - r.Xt, + Yf: height - r.Yf, + Yt: height - r.Yt, + } +} + // IsContainsPoint tests if the Rect contains the Point. func (r Rect) IsContainsPoint(c Point) bool { return c.X >= r.Xf && c.X <= r.Xt && c.Y >= r.Yf && c.Y <= r.Yt @@ -48,6 +57,49 @@ func (r Rect) IsContainsRectangle(t Rect) bool { return r.Xf <= t.Xf && r.Xt >= t.Xt && r.Yf <= t.Yf && r.Yt >= t.Yt } +// IsIntersectsRect tests if the Rect intersects the other Rect. +func (r Rect) IsIntersectsRect(t Rect) bool { + return !(r.Xt < t.Xf || r.Xf > t.Xt || r.Yt < t.Yf || r.Yf > t.Yt) +} + +// RectsIntersection returns the intersection Rect of two Rects. +func (r Rect) RectsIntersection(t Rect) (Rect, bool) { + if !r.IsIntersectsRect(t) { + return Rect{}, false + } + + ir := NewRectangle( + math.Max(r.Xf, t.Xf), + math.Min(r.Xt, t.Xt), + math.Max(r.Yf, t.Yf), + math.Min(r.Yt, t.Yt), + ) + if ir.Width() == 0 || ir.Height() == 0 { + return Rect{}, false + } + + return ir, true +} + +func (r Rect) Vertices() Points { + return Points{ + topLeft0: {r.Xf, r.Yt}, + topRight0: {r.Xt, r.Yt}, + bottomRight0: {r.Xt, r.Yf}, + bottomLeft0: {r.Xf, r.Yf}, + } +} + +// Edges returns the Lines edges of the Rect. +func (r Rect) Edges() Lines { + return Lines{ + top: {Point{r.Xf, r.Yt}, Point{r.Xt, r.Yt}}, + right: {Point{r.Xt, r.Yf}, Point{r.Xt, r.Yt}}, + bottom: {Point{r.Xf, r.Yf}, Point{r.Xt, r.Yf}}, + left: {Point{r.Xf, r.Yf}, Point{r.Xf, r.Yt}}, + } +} + func (r Rect) String() string { return fmt.Sprintf("[X:%.f>%.f,Y:%.f>%.f]", r.Xf, r.Xt, r.Yf, r.Yt) } diff --git a/geometry_rect_test.go b/geometry_rect_test.go index 4af526d..acf9e79 100644 --- a/geometry_rect_test.go +++ b/geometry_rect_test.go @@ -7,81 +7,81 @@ import ( ) func TestRect_IsSame(t *testing.T) { - tests := []struct { - name string - a, b Rect - want bool - }{ - { - name: `true`, - a: Rect{100, 200, 300, 400}, - b: Rect{100, 200, 300, 400}, - want: true, - }, - { - name: `false`, - a: Rect{100, 200, 300, 400}, - b: Rect{300, 400, 100, 200}, - want: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, tc.a.IsSame(tc.b)) - }) - } + var r Rect + r = Rect{100, 200, 300, 400} + assert.True(t, r.IsSame(Rect{100, 200, 300, 400})) + r = Rect{100, 200, 300, 400} + assert.False(t, r.IsSame(Rect{300, 400, 100, 200})) } func TestRect_Width(t *testing.T) { - r := Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400} + r := Rect{100, 200, 300, 400} assert.EqualValues(t, 100, r.Width()) } func TestRect_Height(t *testing.T) { - r := Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400} + r := Rect{100, 200, 300, 400} assert.EqualValues(t, 100, r.Height()) } func TestRect_Area(t *testing.T) { - r := Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400} + r := Rect{100, 200, 300, 400} assert.EqualValues(t, 10000, r.Area()) } func TestRect_Center(t *testing.T) { - r := Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400} + r := Rect{100, 200, 300, 400} assert.Equal(t, Point{150, 350}, r.Center()) } +func TestRect_Symmetric(t *testing.T) { + r := Rect{100, 200, 300, 400} + assert.Equal(t, Rect{900, 800, 700, 600}, r.Symmetric(1000, 1000)) +} + func TestRect_IsContainsPoint(t *testing.T) { + var r Rect + r = Rect{100, 200, 300, 400} + assert.True(t, r.IsContainsPoint(Point{150, 350})) + r = Rect{100, 200, 300, 400} + assert.False(t, r.IsContainsPoint(Point{50, 250})) +} + +func TestRect_IsContainsRectangle(t *testing.T) { tests := []struct { name string r Rect - p Point + t Rect want bool }{ { name: `true`, - r: Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400}, - p: Point{150, 350}, + r: Rect{100, 200, 300, 400}, + t: Rect{120, 180, 320, 380}, want: true, }, { name: `false`, - r: Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400}, - p: Point{50, 250}, + r: Rect{100, 200, 300, 400}, + t: Rect{150, 250, 350, 450}, want: false, }, + { + name: `same`, + r: Rect{100, 200, 300, 400}, + t: Rect{100, 200, 300, 400}, + want: true, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, tc.r.IsContainsPoint(tc.p)) + assert.Equal(t, tc.want, tc.r.IsContainsRectangle(tc.t)) }) } } -func TestRect_IsContainsRectangle(t *testing.T) { +func TestRect_IsIntersectsRect(t *testing.T) { tests := []struct { name string r Rect @@ -90,33 +90,118 @@ func TestRect_IsContainsRectangle(t *testing.T) { }{ { name: `true`, - r: Rect{Xf: 100, Xt: 300, Yf: 300, Yt: 500}, - t: Rect{Xf: 150, Xt: 250, Yf: 350, Yt: 450}, + r: Rect{100, 200, 300, 400}, + t: Rect{150, 250, 350, 450}, want: true, }, { name: `false`, - r: Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400}, - t: Rect{Xf: 150, Xt: 250, Yf: 350, Yt: 450}, + r: Rect{100, 200, 300, 400}, + t: Rect{300, 400, 500, 600}, want: false, }, { name: `same`, - r: Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400}, - t: Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400}, + r: Rect{100, 200, 300, 400}, + t: Rect{100, 200, 300, 400}, + want: true, + }, + { + name: `inside`, + r: Rect{100, 200, 300, 400}, + t: Rect{120, 180, 320, 380}, want: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, tc.r.IsContainsRectangle(tc.t)) + assert.Equal(t, tc.want, tc.r.IsIntersectsRect(tc.t)) + }) + } +} + +func TestRect_RectsIntersection(t *testing.T) { + tests := []struct { + name string + r Rect + t Rect + want Rect + ok bool + }{ + { + name: `true`, + r: Rect{100, 200, 300, 400}, + t: Rect{150, 250, 350, 450}, + want: Rect{150, 200, 350, 400}, + ok: true, + }, + { + name: `same`, + r: Rect{100, 200, 300, 400}, + t: Rect{100, 200, 300, 400}, + want: Rect{100, 200, 300, 400}, + ok: true, + }, + { + name: `second>first`, + r: Rect{100, 200, 300, 400}, + t: Rect{120, 180, 320, 380}, + want: Rect{120, 180, 320, 380}, + ok: true, + }, + { + name: `first>second`, + r: Rect{100, 200, 300, 400}, + t: Rect{80, 220, 280, 420}, + want: Rect{100, 200, 300, 400}, + ok: true, + }, + { + name: `line`, + r: Rect{100, 200, 300, 400}, + t: Rect{200, 300, 300, 400}, + ok: false, + }, + { + name: `point`, + r: Rect{100, 200, 300, 400}, + t: Rect{200, 300, 200, 300}, + ok: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, ok := tc.r.RectsIntersection(tc.t) + assert.Equal(t, tc.ok, ok) + assert.Equal(t, tc.want, got) }) } } -func TestRectangle_String(t *testing.T) { - r := Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400} +func TestRect_Vertices(t *testing.T) { + r := Rect{100, 200, 300, 400} + assert.Equal(t, Points{ + topLeft0: {100, 400}, + topRight0: {200, 400}, + bottomRight0: {200, 300}, + bottomLeft0: {100, 300}, + }, r.Vertices()) +} + +func TestRect_Edges(t *testing.T) { + r := Rect{100, 200, 300, 400} + assert.Equal(t, Lines{ + top: {Point{100, 400}, Point{200, 400}}, + right: {Point{200, 300}, Point{200, 400}}, + bottom: {Point{100, 300}, Point{200, 300}}, + left: {Point{100, 300}, Point{100, 400}}, + }, r.Edges()) +} + +func TestRect_String(t *testing.T) { + r := Rect{100, 200, 300, 400} assert.Equal(t, `[X:100>200,Y:300>400]`, r.String()) } @@ -133,7 +218,7 @@ func TestNewRectangle(t *testing.T) { xt: 200, yf: 300, yt: 400, - want: Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400}, + want: Rect{100, 200, 300, 400}, }, { name: `reverse`, @@ -141,7 +226,7 @@ func TestNewRectangle(t *testing.T) { xt: 100, yf: 400, yt: 300, - want: Rect{Xf: 100, Xt: 200, Yf: 300, Yt: 400}, + want: Rect{100, 200, 300, 400}, }, }