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
use bracket_algorithm_traits::prelude::Algorithm2D;
use bracket_geometry::prelude::{Point, VectorLine, BresenhamCircleNoDiag};
use std::collections::HashSet;
pub fn field_of_view_set(start: Point, range: i32, fov_check: &dyn Algorithm2D) -> HashSet<Point> {
let mut visible_points: HashSet<Point> =
HashSet::with_capacity(((range * 2) * (range * 2)) as usize);
BresenhamCircleNoDiag::new(start, range).for_each(|point| {
scan_fov_line(start, point, fov_check, &mut visible_points);
});
visible_points
}
pub fn field_of_view(start: Point, range: i32, fov_check: &dyn Algorithm2D) -> Vec<Point> {
field_of_view_set(start, range, fov_check)
.into_iter()
.collect()
}
fn scan_fov_line(
start: Point,
end: Point,
fov_check: &dyn Algorithm2D,
visible_points: &mut HashSet<Point>,
) {
let line = VectorLine::new(start, end);
for target in line {
if !fov_check.in_bounds(target) {
break;
}
visible_points.insert(target);
if fov_check.is_opaque(fov_check.point2d_to_index(target)) {
break;
}
}
}
#[cfg(test)]
mod tests {
use bracket_geometry::prelude::{BresenhamCircle, Point};
use bracket_algorithm_traits::prelude::{BaseMap, Algorithm2D};
use crate::prelude::*;
use std::cmp::max;
use std::collections::HashSet;
use std::hash::Hash;
const TESTMAP_W: usize = 20;
const TESTMAP_H: usize = 20;
const TESTMAP_TILES: usize = (TESTMAP_W * TESTMAP_H) as usize;
struct Map {
pub tiles: Vec<bool>,
}
impl Map {
fn new() -> Map {
Map {
tiles: vec![false; TESTMAP_TILES],
}
}
}
impl BaseMap for Map {
fn is_opaque(&self, _idx: usize) -> bool {
false
}
}
impl Algorithm2D for Map {
fn dimensions(&self) -> Point {
Point::new(TESTMAP_W, TESTMAP_H)
}
}
fn has_unique_elements<T>(iter: T) -> bool
where
T: IntoIterator,
T::Item: Eq + Hash,
{
let mut uniq = HashSet::new();
iter.into_iter().all(move |x| uniq.insert(x))
}
#[test]
fn fov_dupes() {
let map = Map::new();
let visible = field_of_view(Point::new(10, 10), 8, &map);
assert!(has_unique_elements(&visible));
}
#[test]
fn fov_bounds_check() {
let map = Map::new();
let visible = field_of_view(Point::new(2, 2), 8, &map);
for t in visible.iter() {
assert!(t.x > 0);
assert!(t.x < TESTMAP_W as i32 - 1);
assert!(t.y > 0);
assert!(t.y < TESTMAP_H as i32 - 1);
}
}
#[test]
fn fov_inclusive() {
let map = Map::new();
let dimensions = map.dimensions();
let c = Point::new(10, 10);
let radius: i32 = 8;
let visible = field_of_view(c, radius, &map);
let max_radius_sq: i32 = BresenhamCircle::new(c, radius).fold(0, |max_r2, p| {
let r2 = (p.x - c.x) * (p.x - c.x) + (p.y - c.y) * (p.y - c.y);
max(r2, max_r2)
});
for x in 0..dimensions.x {
for y in 0..dimensions.y {
let r2 = (x - c.x) * (x - c.x) + (y - c.y) * (y - c.y);
assert!(
r2 >= max_radius_sq || visible.contains(&Point::new(x, y)),
"Interior point not in FOV"
);
}
}
}
}