-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpoint.go
More file actions
120 lines (100 loc) · 2.79 KB
/
Copy pathpoint.go
File metadata and controls
120 lines (100 loc) · 2.79 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
package grid
import (
"slices"
geom "github.com/gravitton/geometry"
"github.com/gravitton/geometry/types/ints"
)
// Point is a grid cell coordinate with spatial query methods (Range, FieldOfView, HasLineOfSight).
type Point ints.Point
// Pt constructs a Point at column x, row y.
func Pt(x, y int) Point {
return Point(ints.Pt(x, y))
}
// DistanceTo returns the grid distance between two points for the given System.
// Cardinal uses Manhattan distance; Diagonal uses Chebyshev distance.
func (p Point) DistanceTo(to ints.Point, system System) int {
return DistanceTo(ints.Point(p), to, system)
}
// Point returns the underlying coordinate as an ints.Point.
func (p Point) Point() ints.Point { return ints.Point(p) }
// Neighbors returns the neighbor coordinates for every direction in system.
func (p Point) Neighbors(system System) []ints.Point {
directions := NeighborOffsets(system)
neighbors := make([]ints.Point, len(directions))
for i, v := range directions {
neighbors[i] = ints.Pt(p.X+v.X, p.Y+v.Y)
}
return neighbors
}
// Range returns all cells within Euclidean distance n from s, inclusive.
func (p Point) Range(n int) []ints.Point {
if n < 0 {
return nil
}
results := make([]ints.Point, 0, (2*n+1)*(2*n+1))
r2 := n * n
for dx := -n; dx <= n; dx++ {
for dy := -n; dy <= n; dy++ {
if dx*dx+dy*dy <= r2 {
results = append(results, geom.Pt(p.X+dx, p.Y+dy))
}
}
}
return results
}
// HasLineOfSight reports whether there is a clear line of sight from src to dst,
// given a set of blocking points. All intermediate cells (excluding src and dst)
// must not be in blocking. The destination itself may be a blocker — it is
// visible but does not allow sight through it.
func (p Point) HasLineOfSight(target ints.Point, blocking []ints.Point) bool {
x0, y0 := ints.Point(p).XY()
x1, y1 := target.XY()
dx := x1 - x0
if dx < 0 {
dx = -dx
}
dy := y1 - y0
if dy < 0 {
dy = -dy
}
sx, sy := 1, 1
if x0 > x1 {
sx = -1
}
if y0 > y1 {
sy = -1
}
x, y, err := x0, y0, dx-dy
for {
if x == x1 && y == y1 {
return true
}
e2 := 2 * err
if e2 > -dy {
err -= dy
x += sx
}
if e2 < dx {
err += dx
y += sy
}
if x == x1 && y == y1 {
return true
}
if slices.Contains(blocking, geom.Pt(x, y)) {
return false
}
}
}
// FieldOfView returns the subset of candidates visible from center,
// given a set of blocking points. Adjacent cells (Chebyshev distance ≤ 1)
// are always visible.
func (p Point) FieldOfView(candidates []ints.Point, blocking []ints.Point) []ints.Point {
results := make([]ints.Point, 0, len(candidates))
for _, candidate := range candidates {
if len(blocking) == 0 || ints.Point(p).ChebyshevDistanceTo(candidate) <= 1 || p.HasLineOfSight(candidate, blocking) {
results = append(results, candidate)
}
}
return results
}