-
Notifications
You must be signed in to change notification settings - Fork 18
/
path.go
238 lines (188 loc) · 6.39 KB
/
path.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
package tetra3d
import (
"strconv"
)
// PathStepper is an object that steps through points in a set path.
// It returns the position of the current node and has the ability to go to the next or previous node in the path.
type PathStepper struct {
path IPath
points []Vector
Index int
}
// NewPathStepper returns a new PathStepper object.
func NewPathStepper(path IPath) *PathStepper {
ps := &PathStepper{}
ps.SetPath(path)
return ps
}
// SetPath sets the path of the PathStepper; this should be called whenever the path updates.
// Doing this will reset the PathStepper's index to 0.
func (ps *PathStepper) SetPath(path IPath) {
ps.path = path
if path != nil {
ps.points = ps.path.Points()
}
ps.SetIndexToStart()
}
// Path returns the path used by the PathStepper.
func (ps *PathStepper) Path() IPath {
return ps.path
}
// SetIndexToStart resets the PathStepper to point to the beginning of the path.
func (ps *PathStepper) SetIndexToStart() {
ps.Index = 0
}
// SetIndexToEnd sets the PathStepper to the point at the end of the path.
func (ps *PathStepper) SetIndexToEnd() {
ps.Index = len(ps.points) - 1
}
// CurrentWorldPosition returns the current node's world position for the PathStepper.
func (ps *PathStepper) CurrentWorldPosition() Vector {
return ps.points[ps.Index]
}
// Next steps to the next point in the Path for the PathStepper.
// If the PathStepper is at the end of the path, then it will loop through the path again.
func (ps *PathStepper) Next() {
ps.Index++
if ps.Index > len(ps.points)-1 {
ps.Index = 0
}
}
// Next steps to the previous point in the Path for the PathStepper.
// If the PathStepper is at the beginning of the path, then it will loop through the path again.
func (ps *PathStepper) Prev() {
ps.Index--
if ps.Index < 0 {
ps.Index = len(ps.points) - 1
}
}
// AtEnd returns if the PathStepper is at the end of its path.
func (ps *PathStepper) AtEnd() bool {
return ps.Index == len(ps.points)-1
}
// AtStart returns if the PathStepper is at the start of its path.
func (ps *PathStepper) AtStart() bool {
return ps.Index == 0
}
// ProgressToWorldPosition returns a position on the PathStepper's current Path, if given a percentage value that ranges from 0 to 1.
// The percentage is weighted for distance, not for number of points.
// For example, say you had a path comprised of four points: {0, 0, 0}, {9, 0, 0}, {9.5, 0, 0}, and {10, 0, 0}. If you called PathStepper.ProgressToWorldPosition(0.9), you'd get {9, 0, 0} (90% of the way through the path).
// If the PathStepper has a nil Path or its Path has no points, this function returns an empty Vector.
func (ps *PathStepper) ProgressToWorldPosition(perc float64) Vector {
if ps.path == nil || len(ps.points) == 0 {
return Vector{}
}
if len(ps.points) == 1 {
return ps.points[0]
}
perc = clamp(perc, 0, 1)
points := append(make([]Vector, 0, len(ps.points)), ps.points...)
if ps.path.isClosed() {
points = append(points, ps.points[0])
}
totalDistance := ps.path.Length()
d := perc
worldPos := points[len(points)-1]
for i := 0; i < len(points)-1; i++ {
segment := points[i+1].Sub(points[i])
segmentDistance := segment.Magnitude() / totalDistance
if d > segmentDistance {
d -= segmentDistance
} else {
worldPos = points[i].Add(segment.Scale(d / segmentDistance))
break
}
}
return worldPos
}
// IPath represents an object that implements a path, returning points of a given length across
// a distance.
type IPath interface {
// Length returns the length of the overall path.
Length() float64
// Points returns the points of the IPath in a slice.
Points() []Vector
HopCount() int // HopCount returns the number of hops in the path.
isClosed() bool
}
// A Path represents a Node that represents a sequential path. All children of the Path are considered its points, in order.
type Path struct {
*Node
Closed bool // Closed indicates if a Path is closed (and so going to the end will return to the start) or not.
PathIndex int // Index of the path points; used to reset the path when navigating through Path.Next().
}
// NewPath returns a new Path object. A Path is a Node whose children represent points on a path. A Path can be stepped through
// spatially using a Navigator. The passed point vectors will become Nodes, children of the Path.
func NewPath(name string, points ...Vector) *Path {
path := &Path{
Node: NewNode(name),
}
path.owner = path
for i, point := range points {
pointNode := NewNode("_point." + strconv.Itoa(i))
pointNode.SetLocalPositionVec(point)
path.AddChildren(pointNode)
}
return path
}
// Clone creates a clone of the Path and its points.
func (path *Path) Clone() INode {
clone := NewPath(path.name)
clone.Closed = path.Closed
clone.Node = path.Node.clone(clone).(*Node)
if clone.Callbacks() != nil && clone.Callbacks().OnClone != nil {
clone.Callbacks().OnClone(clone)
}
return clone
}
// Length returns the total distance that a Path covers by stepping through all of the children under the Path.
func (path *Path) Length() float64 {
dist := 0.0
points := path.Children()
if len(points) <= 1 {
return 0
}
start := points[0].WorldPosition()
for i := 1; i < len(points); i++ {
next := points[i].WorldPosition()
dist += next.Sub(start).Magnitude()
start = next
}
if path.Closed {
dist += points[len(points)-1].WorldPosition().Sub(points[0].WorldPosition()).Magnitude()
}
return dist
}
// Points returns the Vector world positions of each point in the Path.
func (path *Path) Points() []Vector {
points := make([]Vector, 0, len(path.Children()))
for _, c := range path.children {
points = append(points, c.WorldPosition())
}
return points
}
// HopCount returns the number of hops in the path (i.e. number of nodes - 1).
func (path *Path) HopCount() int {
return len(path.Children()) - 1
}
func (path *Path) isClosed() bool {
return path.Closed
}
// Next returns the next point in the path as an iterator - if the boolean value is true, you have reached the end of the path.
// Useful if you don't want to use a Navigator to navigate through it.
func (path *Path) Next() (Vector, bool) {
points := path.Points()
path.PathIndex++
atEnd := false
if path.PathIndex >= len(points) {
path.PathIndex = len(points) - 1
atEnd = true
}
return points[path.PathIndex], atEnd
}
/////
// Type returns the NodeType for this object.
func (path *Path) Type() NodeType {
return NodeTypePath
}
var _ IPath = &Path{} // Sanity check to ensure Path implements IPath.