Added TriangleF.
Renamed Add to Extend on PolygonF{,32}. Add now translates a polygon.
This commit is contained in:
parent
88ed955ce4
commit
e01aa3242d
93
polygonf.go
93
polygonf.go
@ -1,5 +1,7 @@
|
|||||||
package geom
|
package geom
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
// PointsF is a set of points.
|
// PointsF is a set of points.
|
||||||
type PointsF []PointF
|
type PointsF []PointF
|
||||||
|
|
||||||
@ -11,10 +13,97 @@ type PolygonF struct {
|
|||||||
Points PointsF
|
Points PointsF
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add creates a new polyqon based on p with one or more extra points q.
|
// Add adds q as a vector to all points in p.
|
||||||
func (p PolygonF) Add(q ...PointF) PolygonF {
|
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))}
|
var t = PolygonF{make(PointsF, len(p.Points)+len(q))}
|
||||||
copy(t.Points, p.Points)
|
copy(t.Points, p.Points)
|
||||||
copy(t.Points[len(p.Points):], q)
|
copy(t.Points[len(p.Points):], q)
|
||||||
return t
|
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
|
||||||
|
}
|
||||||
|
@ -11,10 +11,34 @@ type PolygonF32 struct {
|
|||||||
Points PointsF32
|
Points PointsF32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add creates a new polyqon based on p with one or more extra points q.
|
// Add adds q as a vector to all points in p.
|
||||||
func (p PolygonF32) Add(q ...PointF32) PolygonF32 {
|
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))}
|
var t = PolygonF32{make(PointsF32, len(p.Points)+len(q))}
|
||||||
copy(t.Points, p.Points)
|
copy(t.Points, p.Points)
|
||||||
copy(t.Points[len(p.Points):], q)
|
copy(t.Points[len(p.Points):], q)
|
||||||
return t
|
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
|
||||||
|
}
|
||||||
|
93
trianglef.go
Normal file
93
trianglef.go
Normal file
@ -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
|
||||||
|
)
|
12
trianglef_test.go
Normal file
12
trianglef_test.go
Normal file
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user