Compare commits

...

5 Commits

Author SHA1 Message Date
e01aa3242d Added TriangleF.
Renamed Add to Extend on PolygonF{,32}.
Add now translates a polygon.
2021-08-09 01:37:16 +02:00
88ed955ce4 Added Atan2, Len, Norm for PointF{,32}.
Added Dot for Point{,F,F32}
2021-08-09 01:35:30 +02:00
701ba39cdc Added Atan2{,32}. 2021-08-09 01:33:48 +02:00
a7a4688687 Add Mul2D to Point{,F,F32}. 2021-08-06 19:11:58 +02:00
e34821ab87 Exposed Pi. 2021-08-06 19:09:26 +02:00
8 changed files with 302 additions and 11 deletions

13
math.go
View File

@ -6,6 +6,9 @@ func emulate32(f float32, fn func(float64) float64) float32 {
return float32(fn(float64(f))) return float32(fn(float64(f)))
} }
// Pi constant https://oeis.org/A000796
const Pi = math.Pi
// Abs returns the absolute value. // Abs returns the absolute value.
func Abs(f float64) float64 { func Abs(f float64) float64 {
return math.Abs(f) return math.Abs(f)
@ -21,11 +24,21 @@ func Atan(f float64) float64 {
return math.Atan(f) return math.Atan(f)
} }
// Atan2 returns the arc tangent of y/x.
func Atan2(y, x float64) float64 {
return math.Atan2(y, x)
}
// Atan32 returns the arc tangent of f. // Atan32 returns the arc tangent of f.
func Atan32(f float32) float32 { func Atan32(f float32) float32 {
return float32(math.Atan(float64(f))) return float32(math.Atan(float64(f)))
} }
// Atan232 returns the arc tangent of y/x.
func Atan232(y, x float32) float32 {
return float32(math.Atan2(float64(y), float64(x)))
}
// Ceil rounds f up to an natural number. // Ceil rounds f up to an natural number.
func Ceil(f float64) float64 { func Ceil(f float64) float64 {
return math.Ceil(f) return math.Ceil(f)

View File

@ -64,6 +64,11 @@ func (p Point) DistInt(q Point) int {
return ints.SubAbs(p.X, q.X) + ints.SubAbs(p.Y, q.Y) return ints.SubAbs(p.X, q.X) + ints.SubAbs(p.Y, q.Y)
} }
// Dot returns the dot product of p and q.
func (p Point) Dot(q Point) int {
return p.X*p.X + p.Y*p.Y
}
// In tests if the point p is inside the rectangle r. // In tests if the point p is inside the rectangle r.
func (p Point) In(r Rectangle) bool { func (p Point) In(r Rectangle) bool {
if p.X < r.Min.X || p.X >= r.Max.X || p.Y < r.Min.Y || p.Y >= r.Max.Y { if p.X < r.Min.X || p.X >= r.Max.X || p.Y < r.Min.Y || p.Y >= r.Max.Y {
@ -80,11 +85,16 @@ func (p Point) Less(q Point) bool {
return p.Y < q.Y return p.Y < q.Y
} }
// Mul multiplier the X and Y values of point p with t and returns the result. // Mul multiplies the X and Y values of point p with t and returns the result.
func (p Point) Mul(t int) Point { func (p Point) Mul(t int) Point {
return Pt(p.X*t, p.Y*t) return Pt(p.X*t, p.Y*t)
} }
// Mul2D multiplies the X and Y values of point p with x and y and returns the result.
func (p Point) Mul2D(x, y int) Point {
return Pt(p.X*x, p.Y*y)
}
// Norm returns the point with both X and Y normalized. // Norm returns the point with both X and Y normalized.
func (p Point) Norm() Point { func (p Point) Norm() Point {
return Pt(ints.Norm(p.X), ints.Norm(p.Y)) return Pt(ints.Norm(p.X), ints.Norm(p.Y))

View File

@ -39,9 +39,14 @@ func (p PointF) AngleTo(q PointF) float64 {
return a return a
} }
// Atan2 returns the arc tangent of y/x.
func (p PointF) Atan2() float64 {
return Atan2(p.Y, p.X)
}
// Distance calculates the distance between points p and q. // Distance calculates the distance between points p and q.
func (p PointF) Distance(q PointF) float64 { func (p PointF) Distance(q PointF) float64 {
return math.Sqrt(p.Distance2(q)) return Sqrt(p.Distance2(q))
} }
// Distance2 calculates the squared distance between points p and q. // Distance2 calculates the squared distance between points p and q.
@ -101,6 +106,11 @@ func (p PointF) Div(t float64) PointF {
return PtF(p.X/t, p.Y/t) return PtF(p.X/t, p.Y/t)
} }
// Dot returns the dot product of p and q.
func (p PointF) Dot(q PointF) float64 {
return p.X*q.X + p.Y*q.Y
}
// In tests if the point p is inside the rectangle r. // In tests if the point p is inside the rectangle r.
func (p PointF) In(r RectangleF) bool { func (p PointF) In(r RectangleF) bool {
if p.X < r.Min.X || p.X >= r.Max.X || p.Y < r.Min.Y || p.Y >= r.Max.Y { if p.X < r.Min.X || p.X >= r.Max.X || p.Y < r.Min.Y || p.Y >= r.Max.Y {
@ -132,11 +142,26 @@ func (p PointF) Invert() PointF {
return PointF{-p.X, -p.Y} return PointF{-p.X, -p.Y}
} }
// Mul multiplier the X and Y values of point p with t and returns the result. // Len returns the length of the vector.
func (p PointF) Len() float64 {
return Sqrt(p.X*p.X + p.Y*p.Y)
}
// Mul multiplies the X and Y values of point p with t and returns the result.
func (p PointF) Mul(t float64) PointF { func (p PointF) Mul(t float64) PointF {
return PtF(p.X*t, p.Y*t) return PtF(p.X*t, p.Y*t)
} }
// Mul2D multiplies the X and Y values of point p with x and y and returns the result.
func (p PointF) Mul2D(x, y float64) PointF {
return PtF(p.X*x, p.Y*y)
}
// Norm returns the normalized vector of x and y.
func (p PointF) Norm() PointF {
return p.Mul(1 / p.Len())
}
// Rect returns a rectangle starting from point p to given point q // Rect returns a rectangle starting from point p to given point q
func (p PointF) Rect(q PointF) RectangleF { func (p PointF) Rect(q PointF) RectangleF {
return RectangleF{Min: p, Max: q} return RectangleF{Min: p, Max: q}

View File

@ -39,9 +39,14 @@ func (p PointF32) AngleTo(q PointF32) float32 {
return a return a
} }
// Atan2 returns the arc tangent of y/x.
func (p PointF32) Atan2() float32 {
return Atan232(p.Y, p.X)
}
// Distance calculates the distance between points p and q. // Distance calculates the distance between points p and q.
func (p PointF32) Distance(q PointF32) float32 { func (p PointF32) Distance(q PointF32) float32 {
return float32(math.Sqrt(float64(p.Distance2(q)))) return Sqrt32(p.Distance2(q))
} }
// Distance2 calculates the squared distance between points p and q. // Distance2 calculates the squared distance between points p and q.
@ -101,6 +106,11 @@ func (p PointF32) Div(t float32) PointF32 {
return PtF32(p.X/t, p.Y/t) return PtF32(p.X/t, p.Y/t)
} }
// Dot returns the dot product of p and q.
func (p PointF32) Dot(q PointF32) float32 {
return p.X*q.X + p.Y*q.Y
}
// In tests if the point p is inside the rectangle r. // In tests if the point p is inside the rectangle r.
func (p PointF32) In(r RectangleF32) bool { func (p PointF32) In(r RectangleF32) bool {
if p.X < r.Min.X || p.X >= r.Max.X || p.Y < r.Min.Y || p.Y >= r.Max.Y { if p.X < r.Min.X || p.X >= r.Max.X || p.Y < r.Min.Y || p.Y >= r.Max.Y {
@ -132,11 +142,26 @@ func (p PointF32) Invert() PointF32 {
return PointF32{-p.X, -p.Y} return PointF32{-p.X, -p.Y}
} }
// Mul multiplier the X and Y values of point p with t and returns the result. // Len returns the length of the vector.
func (p PointF32) Len() float32 {
return Sqrt32(p.X*p.X + p.Y*p.Y)
}
// Mul multiplies the X and Y values of point p with t and returns the result.
func (p PointF32) Mul(t float32) PointF32 { func (p PointF32) Mul(t float32) PointF32 {
return PtF32(p.X*t, p.Y*t) return PtF32(p.X*t, p.Y*t)
} }
// Mul2D multiplies the X and Y values of point p with x and y and returns the result.
func (p PointF32) Mul2D(x, y float32) PointF32 {
return PtF32(p.X*x, p.Y*y)
}
// Norm returns the normalized vector of x and y.
func (p PointF32) Norm() PointF32 {
return p.Mul(1 / p.Len())
}
// Rect returns a rectangle starting from point p to given point q // Rect returns a rectangle starting from point p to given point q
func (p PointF32) Rect(q PointF32) RectangleF32 { func (p PointF32) Rect(q PointF32) RectangleF32 {
return RectangleF32{Min: p, Max: q} return RectangleF32{Min: p, Max: q}

View File

@ -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
}

View File

@ -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
View 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
View 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)
}