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) + } + }) +}