diff --git a/polygonf.go b/polygonf.go index ec01adf..7c05dba 100644 --- a/polygonf.go +++ b/polygonf.go @@ -1,5 +1,7 @@ package geom +import "log" + // PointsF is a set of points. type PointsF []PointF @@ -11,10 +13,97 @@ type PolygonF struct { Points PointsF } -// Add creates a new polyqon based on p with one or more extra points q. -func (p PolygonF) Add(q ...PointF) PolygonF { +// Add adds q as a vector to all points in p. +func (p PolygonF) Add(q PointF) PolygonF { + var r = p.copy() + for i, pt := range r.Points { + r.Points[i] = pt.Add(q) + } + return r +} + +func (p PolygonF) copy() PolygonF { + var q = PolygonF{make(PointsF, len(p.Points))} + copy(q.Points, p.Points) + return q +} + +// Extend creates a new polygon based on p with one or more extra points q. +func (p PolygonF) Extend(q ...PointF) PolygonF { var t = PolygonF{make(PointsF, len(p.Points)+len(q))} copy(t.Points, p.Points) copy(t.Points[len(p.Points):], q) return t } + +// Mul multiplies the points of polygon p with t and returns the result. +func (p PolygonF) Mul(t float64) PolygonF { + var q = p.copy() + for i, pt := range q.Points { + q.Points[i] = pt.Mul(t) + } + return q +} + +// Triangulate triangulates the polygon p. +func (p PolygonF) Triangulate() []TriangleF { + var triangles []TriangleF + points := p.copy().Points[:] + n := len(points) + + if n > 0 && points[0] == points[n-1] { // remove first point if polygon is closed + points = points[1:] + n-- + } + + triangle := func(i int) TriangleF { + return TrF(points[(i+n-1)%n], points[i], points[(i+1)%n]) + } + ear := func(i int) (TriangleF, bool) { + t := triangle(i) + if t.Winding() == WindingCounterClockwise { + return TriangleF{}, false + } + for j := 0; j < n-3; j++ { + p := points[(i+2+j)%n] + if t.Contains(p) { + return TriangleF{}, false + } + } + return t, true + } + + for n >= 3 { + leastSharp := -1 + var leastSharpAngle float64 + for i := range points { + if t, ok := ear(i); ok { + sharpAngle := t.SmallestAngle() + if leastSharp < 0 || sharpAngle > leastSharpAngle { + leastSharp = i + leastSharpAngle = sharpAngle + } + } + } + if leastSharp < 0 { + if n >= 3 { + log.Println("not fully triangulated") + } + break + } + triangles = append(triangles, triangle(leastSharp)) + points = append(points[:leastSharp], points[leastSharp+1:]...) + n = len(points) + } + return triangles +} + +// Reverse reverses the order of the points of polygon p. +func (p PolygonF) Reverse() PolygonF { + n := len(p.Points) + var q = PolygonF{make(PointsF, n)} + for i := 0; i < n/2; i++ { + q.Points[i], q.Points[n-i-1] = p.Points[n-i-1], p.Points[i] + } + return q +} diff --git a/polygonf32.go b/polygonf32.go index 1313eb3..5c71c53 100644 --- a/polygonf32.go +++ b/polygonf32.go @@ -11,10 +11,34 @@ type PolygonF32 struct { Points PointsF32 } -// Add creates a new polyqon based on p with one or more extra points q. -func (p PolygonF32) Add(q ...PointF32) PolygonF32 { +// Add adds q as a vector to all points in p. +func (p PolygonF32) Add(q PointF32) PolygonF32 { + var r = p.copy() + for i, pt := range r.Points { + r.Points[i] = pt.Add(q) + } + return r +} + +func (p PolygonF32) copy() PolygonF32 { + var q = PolygonF32{make(PointsF32, len(p.Points))} + copy(q.Points, p.Points) + return q +} + +// Extend creates a new polygon based on p with one or more extra points q. +func (p PolygonF32) Extend(q ...PointF32) PolygonF32 { var t = PolygonF32{make(PointsF32, len(p.Points)+len(q))} copy(t.Points, p.Points) copy(t.Points[len(p.Points):], q) return t } + +// Mul multiplies the points of polygon p with t and returns the result. +func (p PolygonF32) Mul(t float32) PolygonF32 { + var q = p.copy() + for i, pt := range q.Points { + q.Points[i] = pt.Mul(t) + } + return q +} diff --git a/trianglef.go b/trianglef.go new file mode 100644 index 0000000..6ae3c70 --- /dev/null +++ b/trianglef.go @@ -0,0 +1,93 @@ +package geom + +import ( + "math" +) + +// TriangleF is defined by three points that describe a 2D triangle. +type TriangleF struct { + Points [3]PointF +} + +// TrF is a shorthand method to create a TriangleF. +func TrF(q, r, s PointF) TriangleF { + return TriangleF{Points: [3]PointF{q, r, s}} +} + +// Add translates the triangle by point p. +func (t TriangleF) Add(p PointF) TriangleF { + return TriangleF{Points: [3]PointF{ + t.Points[0].Add(p), + t.Points[1].Add(p), + t.Points[2].Add(p), + }} +} + +// Center gives the average of the three points. +func (t TriangleF) Center() PointF { + return t.Points[0].Add(t.Points[1]).Add(t.Points[2]).Mul(1. / 3) +} + +// Contains tests if point p lies in the triangle. +func (t TriangleF) Contains(p PointF) bool { + u, v, w := t.Points[0], t.Points[1], t.Points[2] + const eps = 1e-9 + q := 1 / (-v.Y*w.X + u.Y*(-v.X+w.X) + u.X*(v.Y-w.Y) + v.X*w.Y) + r := (u.Y*w.X - u.X*w.Y + (w.Y-u.Y)*p.X + (u.X-w.X)*p.Y) * q + s := (u.X*v.Y - u.Y*v.X + (u.Y-v.Y)*p.X + (v.X-u.X)*p.Y) * q + if r < -eps || r > 1+eps || s < -eps || s > 1+eps || r+s > 1+eps { + return false + } + return true +} + +// Inset insets all the points of the triangle towards the center of the triangle with distance f. +func (t TriangleF) Inset(f float64) TriangleF { + center := t.Center() + inset := func(p PointF) PointF { + centered := p.Sub(center) + length := p.Distance(center) + factor := (length - f) / length + return center.Add(centered.Mul(factor)) + } + return TriangleF{Points: [3]PointF{ + inset(t.Points[0]), + inset(t.Points[1]), + inset(t.Points[2]), + }} +} + +// Mul multiplies all the points of the triangle with f. +func (t TriangleF) Mul(f float64) TriangleF { + return TriangleF{Points: [3]PointF{ + t.Points[0].Mul(f), + t.Points[1].Mul(f), + t.Points[2].Mul(f), + }} +} + +// SmallestAngle returns the smallest/sharpest angle of the triangle. +func (t TriangleF) SmallestAngle() float64 { + u, v, w := t.Points[0], t.Points[1], t.Points[2] + q := math.Acos(w.Sub(u).Norm().Dot(v.Sub(u).Norm())) + r := math.Acos(u.Sub(v).Norm().Dot(w.Sub(v).Norm())) + s := math.Acos(v.Sub(w).Norm().Dot(u.Sub(w).Norm())) + return math.Min(q, math.Min(r, s)) +} + +// Winding determines the winding of the triangle. +func (t TriangleF) Winding() Winding { + u, v := t.Points[1].Sub(t.Points[0]), t.Points[2].Sub(t.Points[1]) + if u.X*v.Y-u.Y*v.X > 0 { + return WindingClockwise + } + return WindingCounterClockwise +} + +// Winding describes the order of points. +type Winding bool + +const ( + WindingClockwise Winding = false + WindingCounterClockwise Winding = true +) diff --git a/trianglef_test.go b/trianglef_test.go new file mode 100644 index 0000000..c502c76 --- /dev/null +++ b/trianglef_test.go @@ -0,0 +1,12 @@ +package geom + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTriangleFSmallestAngle(t *testing.T) { + triangle := TrF(PtF(0, 0), PtF(0, 1), PtF(1, 1)) + assert.InEpsilon(t, .25*Pi, triangle.SmallestAngle(), 1e-9) +}