-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvertices.go
149 lines (120 loc) · 4.24 KB
/
vertices.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package numeric
import (
"math"
"slices"
)
type Vertices []Position
// sortVertices returns a function that sorts the vertices of a polygon in clockwise or counter-clockwise order.
// The centroid is the center of the polygon.
// To be used with slices.SortFunc.
func (vertices Vertices) sortVerticesClockwise(clockwise bool) func(i, j Position) int {
sign, c := 1, vertices.Centroid()
if !clockwise {
sign *= -1
}
return func(i, j Position) int {
switch angleI, angleJ := c.AngleTo(i), c.AngleTo(j); {
case
angleI > angleJ, // Largest angle first
angleI == angleJ && c.Distance(i) < c.Distance(j): // Same angle, sort by distance, closest first
return -1 * sign
default:
return 1 * sign
}
}
}
// Axes returns the axes of a polygon.
// The axes are the normals to the edges of the polygon.
// The normals are the perpendicular vectors to the edges.
// It assumes that the vertices are sorted in clockwise or counter-clockwise order.
// If other is supplied, the axes are the normals to the edges of the two polygons.
func (vertices Vertices) Axes(other Vertices) []Position {
axes := make([]Position, 0, len(vertices)+len(other))
for i := 0; i < len(vertices)+len(other); i++ {
var edge Position
if i < len(vertices) {
edge = vertices[i].Sub(vertices[(i+1)%len(vertices)])
} else {
edge = other[i-len(vertices)].Sub(other[(i-len(vertices)+1)%len(other)])
}
axes = append(axes, edge.Perpendicular())
}
return axes
}
// Area calculates the area of a polygon using the Shoelace Formula.
// It assumes that the vertices are sorted in clockwise or counter-clockwise order.
func (vertices Vertices) Area() Number {
var sum Number
for i := range vertices {
product := vertices[i].Cross(vertices[(i+1)%len(vertices)])
sum += product
}
return sum.Abs() / 2
}
// AreSorted checks if the vertices of a polygon are sorted in clockwise or counter-clockwise order.
func (vertices Vertices) AreSorted(clockwise bool) bool {
return slices.IsSortedFunc(vertices, vertices.sortVerticesClockwise(clockwise))
}
// Centroid calculates the centroid of a polygon.
func (vertices Vertices) Centroid() Position {
var sum Position
for _, vertex := range vertices {
sum = sum.Add(vertex)
}
return sum.Div(Number(len(vertices)))
}
// Len returns the number of vertices of a polygon.
func (vertices Vertices) Len() int {
return len(vertices)
}
// Sort sorts the vertices of a polygon in clockwise or counter-clockwise order.
func (vertices Vertices) Sort(clockwise bool) Vertices {
slices.SortFunc(vertices, vertices.sortVerticesClockwise(clockwise))
return vertices
}
// HasSeparatingAxis checks if an object collides with another using the Separating Axis Theorem.
// The axes to test are the normals to the edges of the two objects.
// If there is a separating axis, there is no collision.
// It assumes that the two objects are convex.
func (vertices Vertices) HasSeparatingAxis(other Vertices) bool {
if vertices.Len() == 0 || other.Len() == 0 {
return false
}
// Check for overlap on all axes
for _, axis := range vertices.Axes(other) {
axis = axis.Normalize() // Ensure the axis is normalized
minA, maxA := axis.Project(vertices)
minB, maxB := axis.Project(other)
if minA > maxB || minB > maxA {
// There is a separating axis, no collision
return true
}
}
// No separating axis found, there is a collision
return false
}
// MaximumTranslationVector calculates the minimum translation vector to separate two objects.
// It uses the Separating Axis Theorem to check for collision.
// It assumes that the two objects are convex.
func (vertices Vertices) MinimumTranslationVector(other Vertices) (mtv Position) {
minOverlap := Number(math.MaxFloat64)
// Check for overlap on all axes
for _, axis := range vertices.Axes(other) {
axis = axis.Normalize() // Ensure the axis is normalized
minA, maxA := axis.Project(vertices)
minB, maxB := axis.Project(other)
if minA > maxB || minB > maxA {
// There is a separating axis, no collision
return
}
// Calculate the overlap
if overlap := maxA.Min(maxB) - minA.Max(minB); overlap < minOverlap {
minOverlap = overlap
mtv = axis.Mul(overlap)
if minA < minB { // Change the direction of the MTV in the opposite direction of the axis
mtv = mtv.Mul(-1)
}
}
}
return mtv
}