From 3e1cef52d80c50cb8e0015eba3189fb019ea9b95 Mon Sep 17 00:00:00 2001
From: Gabriele <10689839+vibridi@users.noreply.github.com>
Date: Fri, 27 Sep 2024 09:08:07 +0200
Subject: [PATCH] [geom] Split shapes into individual files; includes a fix to
Tri.Contains
Contains didn't properly account for collinearity.
---
internal/geom/rect.go | 27 ++++++++++++
internal/geom/segment.go | 18 ++++++++
internal/geom/shapes.go | 79 ----------------------------------
internal/geom/shortest.go | 1 -
internal/geom/triangle.go | 52 ++++++++++++++++++++++
internal/geom/triangle_test.go | 25 +++++++++++
6 files changed, 122 insertions(+), 80 deletions(-)
create mode 100644 internal/geom/rect.go
create mode 100644 internal/geom/segment.go
delete mode 100644 internal/geom/shapes.go
create mode 100644 internal/geom/triangle.go
create mode 100644 internal/geom/triangle_test.go
diff --git a/internal/geom/rect.go b/internal/geom/rect.go
new file mode 100644
index 0000000..2e7784b
--- /dev/null
+++ b/internal/geom/rect.go
@@ -0,0 +1,27 @@
+package geom
+
+import "fmt"
+
+// Rect represents a rectangle
+type Rect struct {
+ TL P // top-left vertex of the rectangle
+ BR P // bottom-right vertex of the rectangle
+}
+
+func (r Rect) String() string {
+ return fmt.Sprintf("{geom.P{%.02f,%.02f},geom.P{%.02f,%.02f}}", r.TL.X, r.TL.Y, r.BR.X, r.BR.Y)
+}
+
+func (r Rect) SVG() string {
+ width := r.BR.X - r.TL.X
+ height := r.BR.Y - r.TL.Y
+ return fmt.Sprintf(``, r.TL.X, r.TL.Y, width, height)
+}
+
+func (r Rect) Width() float64 {
+ return r.BR.X - r.TL.X
+}
+
+func (r Rect) Height() float64 {
+ return r.BR.Y - r.TL.Y
+}
diff --git a/internal/geom/segment.go b/internal/geom/segment.go
new file mode 100644
index 0000000..50c8979
--- /dev/null
+++ b/internal/geom/segment.go
@@ -0,0 +1,18 @@
+package geom
+
+import "fmt"
+
+type Segment struct {
+ A, B P
+}
+
+func (seg Segment) SVG() string {
+ return fmt.Sprintf(``, seg.A.X, seg.A.Y, seg.B.X, seg.B.Y)
+}
+
+func (seg Segment) Other(v P) P {
+ if seg.A == v {
+ return seg.B
+ }
+ return seg.A
+}
diff --git a/internal/geom/shapes.go b/internal/geom/shapes.go
deleted file mode 100644
index c569c20..0000000
--- a/internal/geom/shapes.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package geom
-
-import "fmt"
-
-type Segment struct {
- A, B P
-}
-
-func (seg Segment) String() string {
- return fmt.Sprintf(``, seg.A.X, seg.A.Y, seg.B.X, seg.B.Y)
-}
-
-func (seg Segment) Other(v P) P {
- if seg.A == v {
- return seg.B
- }
- return seg.A
-}
-
-// Tri represents a triangle
-type Tri struct {
- ID int
- A, B, C P
-}
-
-func (t Tri) String() string {
- return fmt.Sprintf(``, t.A.X, t.A.Y, t.B.X, t.B.Y, t.C.X, t.C.Y)
- // return fmt.Sprintf(``, t.A.X, t.A.Y, t.B.X, t.B.Y)
-}
-
-func (t Tri) Barycenter() P {
- bx := (t.A.X + t.B.X + t.C.X) / 3
- by := (t.A.Y + t.B.Y + t.C.Y) / 3
- return P{bx, by}
-}
-
-func (t Tri) Contains(p P) bool {
- s := 0
- e := []P{t.A, t.B, t.C}
- for i := 0; i < 3; i++ {
- if orientation(e[i%3], e[(i+1)%3], p) != cw {
- s++
- }
- }
- return s == 3 || s == 0
-}
-
-func (t Tri) OrderedSide(i int) Segment {
- e := []P{t.A, t.B, t.C}
- a, b := e[i%3], e[(i+1)%3]
- if a.X < b.X {
- return Segment{a, b}
- }
- if b.X < a.X {
- return Segment{b, a}
- }
- if a.Y < b.Y {
- return Segment{a, b}
- }
- return Segment{b, a}
-}
-
-// Rect represents a rectangle
-type Rect struct {
- TL P // top-left vertex of the rectangle
- BR P // bottom-right vertex of the rectangle
-}
-
-func (r Rect) String() string {
- return fmt.Sprintf("{geom.P{%.02f,%.02f},geom.P{%.02f,%.02f}}", r.TL.X, r.TL.Y, r.BR.X, r.BR.Y)
-}
-
-func (r Rect) Width() float64 {
- return r.BR.X - r.TL.X
-}
-
-func (r Rect) Height() float64 {
- return r.BR.Y - r.TL.Y
-}
diff --git a/internal/geom/shortest.go b/internal/geom/shortest.go
index ff6df7d..f6e640c 100644
--- a/internal/geom/shortest.go
+++ b/internal/geom/shortest.go
@@ -17,7 +17,6 @@ func Shortest(p1, p2 P, rects []Rect) []P {
// find start and end triangles
var start, end Tri
for _, t := range ts {
- // fmt.Println(t.String())
if t.Contains(p1) {
start = t
}
diff --git a/internal/geom/triangle.go b/internal/geom/triangle.go
new file mode 100644
index 0000000..6ecc881
--- /dev/null
+++ b/internal/geom/triangle.go
@@ -0,0 +1,52 @@
+package geom
+
+import "fmt"
+
+// Tri represents a triangle
+type Tri struct {
+ ID int
+ A, B, C P
+}
+
+func (t Tri) SVG() string {
+ return fmt.Sprintf(``, t.A.X, t.A.Y, t.B.X, t.B.Y, t.C.X, t.C.Y)
+}
+
+func (t Tri) Barycenter() P {
+ bx := (t.A.X + t.B.X + t.C.X) / 3
+ by := (t.A.Y + t.B.Y + t.C.Y) / 3
+ return P{bx, by}
+}
+
+func (t Tri) Contains(p P) bool {
+ s := 0
+ e := []P{t.A, t.B, t.C}
+ for i := range 3 {
+ or := orientation(e[i%3], e[(i+1)%3], p)
+ if or == cln {
+ q, r := e[i%3], e[(i+1)%3]
+ return p.X >= min(q.X, r.X) && p.X <= max(q.X, r.X) &&
+ p.Y >= min(q.Y, r.Y) && p.Y <= max(q.Y, r.Y)
+ }
+
+ if or != cw {
+ s++
+ }
+ }
+ return s == 3 || s == 0
+}
+
+func (t Tri) OrderedSide(i int) Segment {
+ e := []P{t.A, t.B, t.C}
+ a, b := e[i%3], e[(i+1)%3]
+ if a.X < b.X {
+ return Segment{a, b}
+ }
+ if b.X < a.X {
+ return Segment{b, a}
+ }
+ if a.Y < b.Y {
+ return Segment{a, b}
+ }
+ return Segment{b, a}
+}
diff --git a/internal/geom/triangle_test.go b/internal/geom/triangle_test.go
new file mode 100644
index 0000000..e9b3810
--- /dev/null
+++ b/internal/geom/triangle_test.go
@@ -0,0 +1,25 @@
+package geom
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestTri(t *testing.T) {
+ t.Run("contains", func(t *testing.T) {
+ cases := []struct {
+ t Tri
+ p P
+ }{
+ {Tri{0, P{0, 0}, P{20, 0}, P{20, 50}}, P{10, 5}},
+ {Tri{1, P{19.2, 44.7}, P{142.6, 16.5}, P{228, 212}}, P{28.6, 46.6}},
+ {Tri{2, P{20, 20}, P{200, 20}, P{300, 40}}, P{197.7, 27}},
+ {Tri{3, P{50, 10}, P{80, 10}, P{100, 50}}, P{65, 10}}, // collinear parallel x axis
+ {Tri{4, P{255, 210}, P{0, 60}, P{255, 60}}, P{185.615, 169.185}}, // collinear neg slope
+ }
+ for _, c := range cases {
+ assert.Truef(t, c.t.Contains(c.p), "triangle %d does not contain point", c.t.ID)
+ }
+ })
+}