Skip to content

Commit f97e92d

Browse files
committed
Introduce Matrix type and refactor geometry transformations
1 parent e0d1870 commit f97e92d

File tree

12 files changed

+249
-71
lines changed

12 files changed

+249
-71
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1616
- `Rectangle`
1717
- `Polygon`
1818
- `RegularPolygon`
19+
- `Matrix`

internal/slices.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package internal
2+
3+
// Map applies a function to each element in a slice and returns a new slice.
4+
func Map[S ~[]E, E any, T any](input S, fn func(E) T) []T {
5+
output := make([]T, len(input))
6+
for i, v := range input {
7+
output[i] = fn(v)
8+
}
9+
10+
return output
11+
}

math.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ const (
1010

1111
Delta float64 = 1e-6
1212

13-
Sqrt3 = 1.732050807568877293527446341505872367
14-
OneOverSqrtTwo = 1 / math.Sqrt2
13+
Sqrt3 = 1.732050807568877293527446341505872367
1514
)
1615

1716
// ToRadians converts degrees to radians
@@ -79,13 +78,3 @@ func EqualDelta[T Number](a, b T, delta float64) bool {
7978
func equalDelta(a, b, delta float64) bool {
8079
return math.Abs(a-b) <= delta
8180
}
82-
83-
// Transform applies a function to each element in a slice and returns a new slice.
84-
func Transform[S ~[]E, E any, T any](input S, fn func(E) T) []T {
85-
output := make([]T, len(input))
86-
for i, v := range input {
87-
output[i] = fn(v)
88-
}
89-
90-
return output
91-
}

matrix.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package geom
2+
3+
import (
4+
"math"
5+
)
6+
7+
// Matrix is a 2D matrix.
8+
type Matrix struct {
9+
A, B, C float64 // scale X, shear Y, translate X
10+
D, E, F float64 // shear X, scale Y, translate Y
11+
// [0 0 1] implicit third row
12+
}
13+
14+
// M is shorthand for Matrix{a, b, c, d, e, f}.
15+
func M(a, b, c, d, e, f float64) Matrix {
16+
return Matrix{a, b, c, d, e, f}
17+
}
18+
19+
// Multiply creates a new matrix by multiplying the current matrix with given matrix.
20+
func (m Matrix) Multiply(matrix Matrix) Matrix {
21+
return Matrix{
22+
m.A*matrix.A + m.B*matrix.D,
23+
m.A*matrix.B + m.B*matrix.E,
24+
m.A*matrix.C + m.B*matrix.F + m.C,
25+
m.D*matrix.A + m.E*matrix.D,
26+
m.D*matrix.B + m.E*matrix.E,
27+
m.D*matrix.C + m.E*matrix.F + m.F,
28+
}
29+
}
30+
31+
// Inverse creates a new inverse affine matrix. If non-invertible (det ~ 0), returns the same matrix.
32+
func (m Matrix) Inverse() Matrix {
33+
det := m.Determinant()
34+
if Equal(det, 0.0) {
35+
return m
36+
}
37+
38+
invDet := 1.0 / det
39+
return Matrix{
40+
m.E * invDet,
41+
-m.B * invDet,
42+
(m.B*m.F - m.C*m.E) * invDet,
43+
-m.D * invDet,
44+
m.A * invDet,
45+
(m.C*m.D - m.A*m.F) * invDet,
46+
}
47+
}
48+
49+
// Determinant calculates the determinant of the 2x2 matrix.
50+
func (m Matrix) Determinant() float64 {
51+
return m.A*m.E - m.B*m.D
52+
}
53+
54+
// Translate creates a new matrix by translating the current matrix.
55+
func (m Matrix) Translate(deltaX, deltaY float64) Matrix {
56+
return m.Multiply(TranslationMatrix(deltaX, deltaY))
57+
}
58+
59+
// Rotate creates a new matrix by rotating the current matrix.
60+
func (m Matrix) Rotate(angle float64) Matrix {
61+
return m.Multiply(RotationMatrix(angle))
62+
}
63+
64+
// Scale creates a new matrix by scaling the current matrix.
65+
func (m Matrix) Scale(factorX, factorY float64) Matrix {
66+
return m.Multiply(ScaleMatrix(factorX, factorY))
67+
}
68+
69+
// Equal checks for equal values.
70+
func (m Matrix) Equal(other Matrix) bool {
71+
return Equal(m.A, other.A) && Equal(m.B, other.B) && Equal(m.C, other.C) && Equal(m.D, other.D) && Equal(m.E, other.E) && Equal(m.F, other.F)
72+
}
73+
74+
// IsZero checks if values are zero.
75+
func (m Matrix) IsZero() bool { return m.Equal(Matrix{}) }
76+
77+
// IdentityMatrix creates a new identity matrix.
78+
func IdentityMatrix() Matrix {
79+
return Matrix{
80+
1, 0, 0,
81+
0, 1, 0,
82+
}
83+
}
84+
85+
// TranslationMatrix creates a new translation matrix.
86+
func TranslationMatrix(deltaX, deltaY float64) Matrix {
87+
return Matrix{
88+
1, 0, deltaX,
89+
0, 1, deltaY,
90+
}
91+
}
92+
93+
// RotationMatrix creates a new rotation matrix.
94+
func RotationMatrix(angle float64) Matrix {
95+
sin, cos := math.Sincos(angle)
96+
return Matrix{
97+
cos, -sin, 0,
98+
sin, cos, 0,
99+
}
100+
}
101+
102+
// ScaleMatrix creates a new scale matrix.
103+
func ScaleMatrix(factorX, factorY float64) Matrix {
104+
return Matrix{
105+
factorX, 0, 0,
106+
0, factorY, 0,
107+
}
108+
}

point.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ func P[T Number](x, y T) Point[T] {
1515
return Point[T]{x, y}
1616
}
1717

18+
// Transform creates a new Point by applying the given matrix to the current point.
19+
func (p Point[T]) Transform(matrix Matrix) Point[T] {
20+
return Point[T]{
21+
Cast[T](matrix.A*float64(p.X) + matrix.B*float64(p.Y) + matrix.C),
22+
Cast[T](matrix.D*float64(p.X) + matrix.E*float64(p.Y) + matrix.F),
23+
}
24+
}
25+
1826
// Add creates a new Point by adding the given vector to the current point.
1927
func (p Point[T]) Add(vector Vector[T]) Point[T] {
2028
return Point[T]{p.X + vector.X, p.Y + vector.Y}
@@ -80,7 +88,7 @@ func (p Point[T]) Equal(point Point[T]) bool {
8088
return Equal(p.X, point.X) && Equal(p.Y, point.Y)
8189
}
8290

83-
// IsZero checks if X and Y values are 0.
91+
// IsZero checks if X and Y values are zero.
8492
func (p Point[T]) IsZero() bool {
8593
return p.Equal(Point[T]{})
8694
}

polygon.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package geom
22

3+
import "github.com/gravitton/geometry/internal"
4+
35
// Polygon is a 2D polygon with 3+ vertices.
46
type Polygon[T Number] struct {
57
Vertices []Point[T]
@@ -18,7 +20,7 @@ func (p Polygon[T]) Center() Point[T] {
1820

1921
// Translate creates a new Polygon translated by the given vector (applied to all vertices).
2022
func (p Polygon[T]) Translate(vector Vector[T]) Polygon[T] {
21-
return Polygon[T]{Transform(p.Vertices, func(e Point[T]) Point[T] {
23+
return Polygon[T]{internal.Map(p.Vertices, func(e Point[T]) Point[T] {
2224
return e.Add(vector)
2325
})}
2426
}
@@ -31,15 +33,15 @@ func (p Polygon[T]) MoveTo(point Point[T]) Polygon[T] {
3133
// Scale creates a new Polygon uniformly scaled about its centroid by the factor.
3234
func (p Polygon[T]) Scale(factor float64) Polygon[T] {
3335
center := p.Center()
34-
return Polygon[T]{Transform(p.Vertices, func(point Point[T]) Point[T] {
36+
return Polygon[T]{internal.Map(p.Vertices, func(point Point[T]) Point[T] {
3537
return center.Add(point.Subtract(center).Multiply(factor))
3638
})}
3739
}
3840

3941
// ScaleXY creates a new Polygon scaled about its centroid by the factors.
4042
func (p Polygon[T]) ScaleXY(factorX, factorY float64) Polygon[T] {
4143
center := p.Center()
42-
return Polygon[T]{Transform(p.Vertices, func(point Point[T]) Point[T] {
44+
return Polygon[T]{internal.Map(p.Vertices, func(point Point[T]) Point[T] {
4345
return center.Add(point.Subtract(center).MultiplyXY(factorX, factorY))
4446
})}
4547
}

regular_polygon.go

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,55 +9,48 @@ type RegularPolygon[T Number] struct {
99
Center Point[T]
1010
Size Size[T]
1111
N int
12+
Angle float64
1213
}
1314

14-
// RP is shorthand for RegularPolygon{center, size, n}.
15-
func RP[T Number](center Point[T], size Size[T], n int) RegularPolygon[T] {
16-
return RegularPolygon[T]{center, size, n}
17-
}
18-
19-
// Triangle creates a RegularPolygon with 3 vertices.
20-
func Triangle[T Number](center Point[T], size Size[T]) RegularPolygon[T] {
21-
return RegularPolygon[T]{center, size, 3}
22-
}
23-
24-
// Square creates a RegularPolygon with 4 vertices.
25-
func Square[T Number](center Point[T], size Size[T]) RegularPolygon[T] {
26-
return RegularPolygon[T]{center, size, 4}
27-
}
28-
29-
// Hexagon creates a RegularPolygon with 6 vertices.
30-
func Hexagon[T Number](center Point[T], size Size[T]) RegularPolygon[T] {
31-
return RegularPolygon[T]{center, size, 6}
15+
// RP is shorthand for RegularPolygon{center, size, n, angle}.
16+
func RP[T Number](center Point[T], size Size[T], n int, angle float64) RegularPolygon[T] {
17+
return RegularPolygon[T]{center, size, n, angle}
3218
}
3319

3420
// Translate creates a new RegularPolygon translated by the given vector.
3521
func (rp RegularPolygon[T]) Translate(change Vector[T]) RegularPolygon[T] {
36-
return RegularPolygon[T]{rp.Center.Add(change), rp.Size, rp.N}
22+
return RegularPolygon[T]{rp.Center.Add(change), rp.Size, rp.N, rp.Angle}
3723
}
3824

3925
// MoveTo creates a new RegularPolygon with center at point.
4026
func (rp RegularPolygon[T]) MoveTo(point Point[T]) RegularPolygon[T] {
41-
return RegularPolygon[T]{point, rp.Size, rp.N}
27+
return RegularPolygon[T]{point, rp.Size, rp.N, rp.Angle}
4228
}
4329

4430
// Multiple creates a new RegularPolygon with size scaled by the given factor.
4531
func (rp RegularPolygon[T]) Scale(factor float64) RegularPolygon[T] {
46-
return RegularPolygon[T]{rp.Center, rp.Size.Scale(factor), rp.N}
32+
return RegularPolygon[T]{rp.Center, rp.Size.Scale(factor), rp.N, rp.Angle}
4733
}
4834

4935
// Multiple creates a new RegularPolygon with size scaled by the given factors.
5036
func (rp RegularPolygon[T]) ScaleXY(factorX, factorY float64) RegularPolygon[T] {
51-
return RegularPolygon[T]{rp.Center, rp.Size.ScaleXY(factorX, factorY), rp.N}
37+
return RegularPolygon[T]{rp.Center, rp.Size.ScaleXY(factorX, factorY), rp.N, rp.Angle}
38+
}
39+
40+
// Rotate creates a new RegularPolygon rotated by the given angle (in radians).
41+
func (rp RegularPolygon[T]) Rotate(angle float64) RegularPolygon[T] {
42+
// TODO: normalize angle to [0, 2*pi]
43+
return RegularPolygon[T]{rp.Center, rp.Size, rp.N, rp.Angle + angle}
5244
}
5345

5446
// Vertices returns the polygon vertices in order starting from angle 0, counter-clockwise.
5547
func (rp RegularPolygon[T]) Vertices() []Point[T] {
56-
initAngle := 0.0
57-
angleStep := 2 * math.Pi / float64(rp.N)
48+
initAngle := rp.Angle
49+
angleStep := (2 * math.Pi) / float64(rp.N)
5850

5951
vertices := make([]Point[T], rp.N)
6052
for i := 0; i < rp.N; i++ {
53+
// TODO: V(1,0).Rotate(angle)
6154
vertices[i] = rp.Center.Add(VecFromAngle[T](initAngle+float64(i)*angleStep, 1).MultiplyXY(float64(rp.Size.Width), float64(rp.Size.Height)))
6255
}
6356

@@ -66,7 +59,9 @@ func (rp RegularPolygon[T]) Vertices() []Point[T] {
6659

6760
// Bounds returns the axis-aligned bounding rectangle.
6861
func (rp RegularPolygon[T]) Bounds() Rectangle[T] {
69-
return Rectangle[T]{rp.Center, rp.Size}
62+
// TODO: calculate
63+
maxAbsCos, maxAbsSin := 1.0, 1.0
64+
return Rectangle[T]{rp.Center, rp.Size.ScaleXY(2.0*maxAbsCos, 2.0*maxAbsSin)}
7065
}
7166

7267
// ToPolygon converts the regular polygon into a generic Polygon with computed vertices.
@@ -88,3 +83,38 @@ func (rp RegularPolygon[T]) IsZero() bool {
8883
func (rp RegularPolygon[T]) Empty() bool {
8984
return rp.N == 0
9085
}
86+
87+
type Orientation int
88+
89+
const (
90+
FlatTop Orientation = iota
91+
PointTop
92+
)
93+
94+
func RegularPolygonAngle(n int, orientation Orientation) float64 {
95+
switch orientation {
96+
case FlatTop:
97+
// 90 - 180/n degrees
98+
return math.Pi * float64(n-2) / (2 * float64(n))
99+
case PointTop:
100+
// 90 degrees
101+
return math.Pi / 2
102+
default:
103+
return 0
104+
}
105+
}
106+
107+
// Triangle creates a RegularPolygon with 3 vertices.
108+
func Triangle[T Number](center Point[T], size Size[T], orientation Orientation) RegularPolygon[T] {
109+
return RegularPolygon[T]{center, size, 3, RegularPolygonAngle(3, orientation)}
110+
}
111+
112+
// Square creates a RegularPolygon with 4 vertices.
113+
func Square[T Number](center Point[T], size Size[T], orientation Orientation) RegularPolygon[T] {
114+
return RegularPolygon[T]{center, size, 4, RegularPolygonAngle(4, orientation)}
115+
}
116+
117+
// Hexagon creates a RegularPolygon with 6 vertices.
118+
func Hexagon[T Number](center Point[T], size Size[T], orientation Orientation) RegularPolygon[T] {
119+
return RegularPolygon[T]{center, size, 6, RegularPolygonAngle(6, orientation)}
120+
}

0 commit comments

Comments
 (0)