-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdirection.go
More file actions
317 lines (275 loc) · 9.36 KB
/
Copy pathdirection.go
File metadata and controls
317 lines (275 loc) · 9.36 KB
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
package geom
import (
"fmt"
"math"
)
// Direction is one of the eight neighbor directions on a square lattice.
//
// Directions are numbered by increasing normalized angle, matching Angle and Vector.Angle: counterclockwise
// in the standard math convention where Y grows upward, which appears clockwise as drawn on a
// screen with Y pointing down. A negative step or angle is therefore counterclockwise on screen.
type Direction int
const (
// DirectionRight points along +X, at angle 0.
DirectionRight Direction = iota
// DirectionDownRight points along +X and +Y, at angle π/4.
DirectionDownRight
// DirectionDown points along +Y, at angle π/2.
DirectionDown
// DirectionDownLeft points along -X and +Y, at angle 3π/4.
DirectionDownLeft
// DirectionLeft points along -X, at angle π.
DirectionLeft
// DirectionUpLeft points along -X and -Y, at angle -3π/4.
DirectionUpLeft
// DirectionUp points along -Y, at angle -π/2.
DirectionUp
// DirectionUpRight points along +X and -Y, at angle -π/4.
DirectionUpRight
// DirectionNone is the absence of a direction.
DirectionNone Direction = -1
)
// Direction aliases for lattice and map code.
const (
East = DirectionRight
NorthEast = DirectionUpRight
North = DirectionUp
NorthWest = DirectionUpLeft
West = DirectionLeft
SouthWest = DirectionDownLeft
South = DirectionDown
SouthEast = DirectionDownRight
)
// Direction aliases for the edges and corners of an axis-aligned rectangle.
const (
Top = DirectionUp
Bottom = DirectionDown
Left = DirectionLeft
Right = DirectionRight
TopRight = DirectionUpRight
TopLeft = DirectionUpLeft
BottomLeft = DirectionDownLeft
BottomRight = DirectionDownRight
)
// Directions lists all eight directions ordered by increasing angle from DirectionRight.
// It returns a fresh array, so a caller cannot alter the list.
func Directions() [8]Direction {
return [8]Direction{DirectionRight, DirectionDownRight, DirectionDown, DirectionDownLeft, DirectionLeft, DirectionUpLeft, DirectionUp, DirectionUpRight}
}
// CardinalDirections lists the four cardinal directions ordered by increasing angle from DirectionRight.
// It returns a fresh array, so a caller cannot alter the list.
func CardinalDirections() [4]Direction {
return [4]Direction{DirectionRight, DirectionDown, DirectionLeft, DirectionUp}
}
// DiagonalDirections lists the four diagonal directions ordered by increasing angle from DirectionDownRight.
// It returns a fresh array, so a caller cannot alter the list.
func DiagonalDirections() [4]Direction {
return [4]Direction{DirectionDownRight, DirectionDownLeft, DirectionUpLeft, DirectionUpRight}
}
// directionOffsets lists the lattice step of each direction, indexed by direction.
var directionOffsets = [8]Vector[int]{
{1, 0}, // Right
{1, 1}, // DownRight
{0, 1}, // Down
{-1, 1}, // DownLeft
{-1, 0}, // Left
{-1, -1}, // UpLeft
{0, -1}, // Up
{1, -1}, // UpRight
}
// DirectionFromAngle returns the direction nearest to the given angle in radians,
// or DirectionNone for NaN and ±Inf.
func DirectionFromAngle(angle float64) Direction {
if math.IsNaN(angle) || math.IsInf(angle, 0) {
return DirectionNone
}
return Mod(Direction(math.Round(NormalizeAngle(angle)/(Pi/4))), 8)
}
// DirectionFromAxes returns the direction for the given axis inputs, canceling opposite
// directions (e.g. left+right = DirectionNone).
func DirectionFromAxes(up, down, left, right bool) Direction {
if left && right {
left, right = false, false
}
if up && down {
up, down = false, false
}
switch {
case up && left:
return DirectionUpLeft
case up && right:
return DirectionUpRight
case down && left:
return DirectionDownLeft
case down && right:
return DirectionDownRight
case up:
return DirectionUp
case down:
return DirectionDown
case left:
return DirectionLeft
case right:
return DirectionRight
default:
return DirectionNone
}
}
// ParseDirection returns the direction with the given name, as String prints it, and an error
// for any other string. "None" parses to DirectionNone.
func ParseDirection(name string) (Direction, error) {
if name == DirectionNone.String() {
return DirectionNone, nil
}
for _, direction := range Directions() {
if direction.String() == name {
return direction, nil
}
}
return DirectionNone, fmt.Errorf("geom: unknown direction %q", name)
}
// Opposite returns the opposite direction, rotated 180°.
func (d Direction) Opposite() Direction {
return d.Turn(4)
}
// Turn advances the direction by steps eighth-turns of increasing angle, the same sense as a
// positive Vector.Rotate angle: counterclockwise in math coordinates, clockwise as drawn on a
// screen with Y pointing down. Negative steps go the other way.
func (d Direction) Turn(steps int) Direction {
if d.IsNone() {
return DirectionNone
}
return Mod(d+Direction(steps), 8)
}
// Axis returns the axis the direction runs on, or AxisNone for diagonals and DirectionNone.
func (d Direction) Axis() Axis {
switch d.normalize() {
case DirectionRight, DirectionLeft:
return AxisHorizontal
case DirectionUp, DirectionDown:
return AxisVertical
default:
return AxisNone
}
}
// Offset creates a new non-normalized Vector with the lattice step of the direction, and the zero vector for DirectionNone.
func (d Direction) Offset[T Number]() Vector[T] {
offset := d.offset()
return Vector[T]{T(offset.X), T(offset.Y)}
}
// Unit creates a new normalized Vector in the direction, and the zero vector for DirectionNone.
// For integer T, only the four axis-aligned vectors have length 1, so a diagonal collapses onto
// one of them; use Offset for the lattice step (±1,±1) that keeps the diagonal.
func (d Direction) Unit[T Number]() Vector[T] {
if d.IsNone() {
return Vector[T]{}
}
return d.Offset[T]().Normalize()
}
// Vector creates a new Vector of the given length pointing in the direction, and the zero vector for DirectionNone.
// A negative length points the other way, as Vector.Resize gives it, so the result runs along
// the opposite direction with the absolute length.
// For integer T, a diagonal has both components rounded, so its actual length is only
// approximate. The diagonal is kept, unlike Unit, which snaps to an axis: a diagonal of unit
// length is the lattice step of the direction, not the axis vector Unit gives.
func (d Direction) Vector[T Number](length T) Vector[T] {
if d.IsNone() {
return Vector[T]{}
}
return d.Offset[T]().Resize(float64(length))
}
// Angle returns the angle of the direction in radians, measured in the standard math
// convention where Y grows upward — so DirectionUp is -Pi/2, not +Pi/2, and NaN for
// DirectionNone, which has no angle. Direction ordering follows this angle normalized to
// [0, 2π), so DirectionFromAngle and Angle round-trip for every direction, DirectionNone included.
func (d Direction) Angle() float64 {
if d.IsNone() {
return math.NaN()
}
return d.Offset[float64]().Angle()
}
// normalize returns the direction wrapped into [DirectionRight, DirectionUpRight],
// preserving DirectionNone.
func (d Direction) normalize() Direction {
if d.IsNone() {
return DirectionNone
}
return Mod(d, 8)
}
// offset returns the lattice step of the direction, or a zero step for DirectionNone.
func (d Direction) offset() Vector[int] {
if d.IsNone() {
return Vector[int]{}
}
return directionOffsets[d.normalize()]
}
// IsNone reports whether the direction is DirectionNone.
func (d Direction) IsNone() bool {
return d == DirectionNone
}
// IsCardinal reports whether the direction is one of DirectionRight, DirectionUp, DirectionLeft, or DirectionDown.
func (d Direction) IsCardinal() bool {
switch d.normalize() {
case DirectionRight, DirectionUp, DirectionLeft, DirectionDown:
return true
default:
return false
}
}
// IsDiagonal reports whether the direction is one of DirectionUpRight, DirectionUpLeft, DirectionDownLeft, or DirectionDownRight.
func (d Direction) IsDiagonal() bool {
switch d.normalize() {
case DirectionUpRight, DirectionUpLeft, DirectionDownLeft, DirectionDownRight:
return true
default:
return false
}
}
// IsPositive reports whether the direction runs toward growing coordinates on its axis:
// DirectionRight and DirectionDown, since Y grows downward.
// It is false for DirectionNone and for diagonals, which run on neither axis.
func (d Direction) IsPositive() bool {
switch d.normalize() {
case DirectionRight, DirectionDown:
return true
default:
return false
}
}
// String returns the name of the direction constant.
func (d Direction) String() string {
switch d.normalize() {
case DirectionRight:
return "Right"
case DirectionUpRight:
return "UpRight"
case DirectionUp:
return "Up"
case DirectionUpLeft:
return "UpLeft"
case DirectionLeft:
return "Left"
case DirectionDownLeft:
return "DownLeft"
case DirectionDown:
return "Down"
case DirectionDownRight:
return "DownRight"
default:
return "None"
}
}
// MarshalText implements encoding.TextMarshaler with the name String prints, so a direction
// is stored as "UpRight" in JSON and as a map key rather than as its number.
func (d Direction) MarshalText() ([]byte, error) {
return []byte(d.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler, the inverse of MarshalText through ParseDirection.
func (d *Direction) UnmarshalText(text []byte) error {
direction, err := ParseDirection(string(text))
if err != nil {
return err
}
*d = direction
return nil
}