bracket_pathfinding/field_of_view/
recursive_shadowcasting.rs1use bracket_algorithm_traits::prelude::Algorithm2D;
2use bracket_geometry::prelude::Point;
4use std::collections::HashSet;
5
6struct ScanFovData<'a> {
7 center: Point,
8 dimensions: Point,
9 range_2: i32,
10 fov_check: &'a dyn Algorithm2D,
11 visible_points: &'a mut HashSet<Point>,
12}
13
14#[allow(non_snake_case)]
15impl ScanFovData<'_> {
16 fn is_transparent(&self, idx: usize, point: Point) -> bool {
17 if self.fov_check.in_bounds(point) {
18 !self.fov_check.is_opaque(idx)
19 } else {
20 false
21 }
22 }
23
24 fn distance_to_center(&self, point: Point) -> f32 {
25 let dx = i32::abs(point.x - self.center.x) as f32 - 0.5;
26 let dy = i32::abs(point.y - self.center.y) as f32 - 0.5;
27 dx * dx + dy * dy
28 }
29
30 fn insert_visible_for_vertical(&mut self, point: Point) -> bool {
31 let idx = self.fov_check.point2d_to_index(point);
32 let mut is_visible = self.is_transparent(idx, point);
33
34 if self.distance_to_center(point) <= self.range_2 as f32 {
35 if point.x != self.center.x {
36 self.visible_points.insert(point);
37 }
38 } else {
39 is_visible = false;
40 }
41 is_visible
42 }
43
44 fn insert_visible_for_horizontal(&mut self, point: Point) -> bool {
45 let idx = self.fov_check.point2d_to_index(point);
46 let mut is_visible = self.is_transparent(idx, point);
47
48 if self.distance_to_center(point) <= self.range_2 as f32 {
49 if self.center.y != point.y {
50 self.visible_points.insert(point);
51 }
52 } else {
53 is_visible = false;
54 }
55 is_visible
56 }
57
58 fn scan_N2NE(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
59 let mut start_slope = start_slope;
60
61 if distance * distance > self.range_2 {
62 return;
63 }
64
65 let mut current = Point::new(0, self.center.y - distance);
66 if current.y < 0 {
67 return;
68 }
69
70 current.x = self.center.x + (start_slope * distance as f32 + 0.5) as i32;
71 if current.x >= self.dimensions.x {
72 return;
73 }
74
75 let mut end_x = self.center.x + (end_slope * distance as f32 + 0.5) as i32;
76 if end_x >= self.dimensions.x {
77 end_x = self.dimensions.x - 1;
78 }
79
80 let idx = self.fov_check.point2d_to_index(current);
81 let mut last_visible = self.is_transparent(idx, current);
82 for current_x in current.x..=end_x {
83 current.x = current_x;
84
85 let is_visible = self.insert_visible_for_vertical(current);
86
87 if last_visible && !is_visible {
88 self.scan_N2NE(
89 distance + 1,
90 start_slope,
91 ((current.x - self.center.x) as f32 - 0.5) / (distance as f32 + 0.5),
92 );
93 } else if !last_visible && is_visible {
94 start_slope = ((current.x - self.center.x) as f32 - 0.5) / (distance as f32 - 0.5);
95 }
96 last_visible = is_visible;
97 }
98 if last_visible {
99 self.scan_N2NE(distance + 1, start_slope, end_slope);
100 }
101 }
102
103 fn scan_N2NW(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
104 let mut start_slope = start_slope;
105
106 if distance * distance > self.range_2 {
107 return;
108 }
109
110 let mut current = Point::new(0, self.center.y - distance);
111 if current.y < 0 {
112 return;
113 }
114
115 current.x = self.center.x - (start_slope * distance as f32 + 0.5) as i32;
116 if current.x < 0 {
117 return;
118 }
119
120 let mut end_x = self.center.x - (end_slope * distance as f32 + 0.5) as i32;
121 if end_x < 0 {
122 end_x = 0;
123 }
124
125 let idx = self.fov_check.point2d_to_index(current);
126 let mut last_visible = self.is_transparent(idx, current);
127 while current.x >= end_x {
128 let is_visible = self.insert_visible_for_vertical(current);
129
130 if last_visible && !is_visible {
131 self.scan_N2NW(
132 distance + 1,
133 start_slope,
134 ((self.center.x - current.x) as f32 - 0.5) / (distance as f32 + 0.5),
135 );
136 } else if !last_visible && is_visible {
137 start_slope = ((self.center.x - current.x) as f32 - 0.5) / (distance as f32 - 0.5);
138 }
139 last_visible = is_visible;
140 current.x -= 1;
141 }
142 if last_visible {
143 self.scan_N2NW(distance + 1, start_slope, end_slope);
144 }
145 }
146
147 fn scan_S2SE(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
148 let mut start_slope = start_slope;
149
150 if distance * distance > self.range_2 {
151 return;
152 }
153
154 let mut current = Point::new(0, self.center.y + distance);
155 if current.y >= self.dimensions.y {
156 return;
157 }
158
159 current.x = self.center.x + (start_slope * distance as f32 + 0.5) as i32;
160 if current.x >= self.dimensions.x {
161 return;
162 }
163
164 let mut end_x = self.center.x + (end_slope * distance as f32 + 0.5) as i32;
165 if end_x >= self.dimensions.x {
166 end_x = self.dimensions.x - 1;
167 }
168
169 let idx = self.fov_check.point2d_to_index(current);
170 let mut last_visible = self.is_transparent(idx, current);
171 for current_x in current.x..=end_x {
172 current.x = current_x;
173
174 let is_visible = self.insert_visible_for_vertical(current);
175
176 if last_visible && !is_visible {
177 self.scan_S2SE(
178 distance + 1,
179 start_slope,
180 ((current.x - self.center.x) as f32 - 0.5) / (distance as f32 + 0.5),
181 );
182 } else if !last_visible && is_visible {
183 start_slope = ((current.x - self.center.x) as f32 - 0.5) / (distance as f32 - 0.5);
184 }
185 last_visible = is_visible;
186 }
187 if last_visible {
188 self.scan_S2SE(distance + 1, start_slope, end_slope);
189 }
190 }
191
192 fn scan_S2SW(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
193 let mut start_slope = start_slope;
194
195 if distance * distance > self.range_2 {
196 return;
197 }
198
199 let mut current = Point::new(0, self.center.y + distance);
200 if current.y >= self.dimensions.y {
201 return;
202 }
203
204 current.x = self.center.x - (start_slope * distance as f32 + 0.5) as i32;
205 if current.x < 0 {
206 return;
207 }
208
209 let mut end_x = self.center.x - (end_slope * distance as f32 + 0.5) as i32;
210 if end_x < 0 {
211 end_x = 0;
212 }
213
214 let idx = self.fov_check.point2d_to_index(current);
215 let mut last_visible = self.is_transparent(idx, current);
216 while current.x >= end_x {
217 let is_visible = self.insert_visible_for_vertical(current);
218
219 if last_visible && !is_visible {
220 self.scan_S2SW(
221 distance + 1,
222 start_slope,
223 ((self.center.x - current.x) as f32 - 0.5) / (distance as f32 + 0.5),
224 );
225 } else if !last_visible && is_visible {
226 start_slope = ((self.center.x - current.x) as f32 - 0.5) / (distance as f32 - 0.5);
227 }
228 last_visible = is_visible;
229 current.x -= 1;
230 }
231 if last_visible {
232 self.scan_S2SW(distance + 1, start_slope, end_slope);
233 }
234 }
235
236 fn scan_E2SE(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
237 let mut start_slope = start_slope;
238
239 if distance * distance > self.range_2 {
240 return;
241 }
242
243 let mut current = Point::new(self.center.x + distance, 0);
244 if current.x >= self.dimensions.x {
245 return;
246 }
247
248 current.y = self.center.y + (start_slope * distance as f32 + 0.5) as i32;
249 if current.y >= self.dimensions.y {
250 return;
251 }
252
253 let mut end_y = self.center.y + (end_slope * distance as f32 + 0.5) as i32;
254 if end_y >= self.dimensions.y {
255 end_y = self.dimensions.y - 1;
256 }
257
258 let idx = self.fov_check.point2d_to_index(current);
259 let mut last_visible = self.is_transparent(idx, current);
260 for current_y in current.y..=end_y {
261 current.y = current_y;
262
263 let is_visible = self.insert_visible_for_horizontal(current);
264
265 if last_visible && !is_visible {
266 self.scan_E2SE(
267 distance + 1,
268 start_slope,
269 ((current.y - self.center.y) as f32 - 0.5) / (distance as f32 + 0.5),
270 );
271 } else if !last_visible && is_visible {
272 start_slope = ((current.y - self.center.y) as f32 - 0.5) / (distance as f32 - 0.5);
273 }
274 last_visible = is_visible;
275 }
276 if last_visible {
277 self.scan_E2SE(distance + 1, start_slope, end_slope);
278 }
279 }
280
281 fn scan_E2NE(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
282 let mut start_slope = start_slope;
283
284 if distance * distance > self.range_2 {
285 return;
286 }
287
288 let mut current = Point::new(self.center.x + distance, 0);
289 if current.x >= self.dimensions.x {
290 return;
291 }
292
293 current.y = self.center.y - (start_slope * distance as f32 + 0.5) as i32;
294 if current.y < 0 {
295 return;
296 }
297
298 let mut end_y = self.center.y - (end_slope * distance as f32 + 0.5) as i32;
299 if end_y < 0 {
300 end_y = 0;
301 }
302
303 let idx = self.fov_check.point2d_to_index(current);
304 let mut last_visible = self.is_transparent(idx, current);
305 while current.y >= end_y {
306 let is_visible = self.insert_visible_for_horizontal(current);
307
308 if last_visible && !is_visible {
309 self.scan_E2NE(
310 distance + 1,
311 start_slope,
312 ((self.center.y - current.y) as f32 - 0.5) / (distance as f32 + 0.5),
313 );
314 } else if !last_visible && is_visible {
315 start_slope = ((self.center.y - current.y) as f32 - 0.5) / (distance as f32 - 0.5);
316 }
317 last_visible = is_visible;
318 current.y -= 1;
319 }
320 if last_visible {
321 self.scan_E2NE(distance + 1, start_slope, end_slope);
322 }
323 }
324
325 fn scan_W2SW(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
326 let mut start_slope = start_slope;
327
328 if distance * distance > self.range_2 {
329 return;
330 }
331
332 let mut current = Point::new(self.center.x - distance, 0);
333 if current.x < 0 {
334 return;
335 }
336
337 current.y = self.center.y + (start_slope * distance as f32 + 0.5) as i32;
338 if current.y >= self.dimensions.y {
339 return;
340 }
341
342 let mut end_y = self.center.y + (end_slope * distance as f32 + 0.5) as i32;
343 if end_y >= self.dimensions.y {
344 end_y = self.dimensions.y - 1;
345 }
346
347 let idx = self.fov_check.point2d_to_index(current);
348 let mut last_visible = self.is_transparent(idx, current);
349 for current_y in current.y..=end_y {
350 current.y = current_y;
351
352 let is_visible = self.insert_visible_for_horizontal(current);
353
354 if last_visible && !is_visible {
355 self.scan_W2SW(
356 distance + 1,
357 start_slope,
358 ((current.y - self.center.y) as f32 - 0.5) / (distance as f32 + 0.5),
359 );
360 } else if !last_visible && is_visible {
361 start_slope = ((current.y - self.center.y) as f32 - 0.5) / (distance as f32 - 0.5);
362 }
363 last_visible = is_visible;
364 }
365 if last_visible {
366 self.scan_W2SW(distance + 1, start_slope, end_slope);
367 }
368 }
369
370 fn scan_W2NW(&mut self, distance: i32, start_slope: f32, end_slope: f32) {
371 let mut start_slope = start_slope;
372
373 if distance * distance > self.range_2 {
374 return;
375 }
376
377 let mut current = Point::new(self.center.x - distance, 0);
378 if current.x < 0 {
379 return;
380 }
381
382 current.y = self.center.y - (start_slope * distance as f32 + 0.5) as i32;
383 if current.y < 0 {
384 return;
385 }
386
387 let mut end_y = self.center.y - (end_slope * distance as f32 + 0.5) as i32;
388 if end_y < 0 {
389 end_y = 0;
390 }
391
392 let idx = self.fov_check.point2d_to_index(current);
393 let mut last_visible = self.is_transparent(idx, current);
394 while current.y >= end_y {
395 let is_visible = self.insert_visible_for_horizontal(current);
396
397 if last_visible && !is_visible {
398 self.scan_W2NW(
399 distance + 1,
400 start_slope,
401 ((self.center.y - current.y) as f32 - 0.5) / (distance as f32 + 0.5),
402 );
403 } else if !last_visible && is_visible {
404 start_slope = ((self.center.y - current.y) as f32 - 0.5) / (distance as f32 - 0.5);
405 }
406 last_visible = is_visible;
407 current.y -= 1;
408 }
409 if last_visible {
410 self.scan_W2NW(distance + 1, start_slope, end_slope);
411 }
412 }
413}
414
415#[rustfmt::skip]
418pub fn field_of_view_set(center: Point, range: i32, fov_check: &dyn Algorithm2D) -> HashSet<Point> {
419 let mut visible_points: HashSet<Point> =
420 HashSet::with_capacity(((range * 2) * (range * 2)) as usize);
421
422 visible_points.insert(center);
423
424 const SECTORS: [(i32, i32); 8] = [ (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), ];
426
427 let r2 = range * range;
428 let dimensions = fov_check.dimensions();
429
430 let mut visibility_per_sector = [false; 8];
432 for (i, (dx, dy)) in SECTORS.iter().enumerate() {
433 let mut current = center;
434 loop {
435 current = Point::new(current.x + dx, current.y + dy);
436 if current.x < 0 || current.x >= dimensions.x
437 || current.y < 0 || current.y >= dimensions.y
438 {
439 break;
440 }
441 let x2 = current.x - center.x;
442 let x2 = x2 * x2;
443 let y2 = current.y - center.y;
444 let y2 = y2 * y2;
445 if x2 + y2 > r2 {
446 break;
447 }
448
449 let idx = fov_check.point2d_to_index(current);
450 visible_points.insert(current);
451 if fov_check.is_opaque(idx) {
452 break;
453 }
454 visibility_per_sector[i] = true;
455 }
456 }
457
458 let mut scanner = ScanFovData {
459 center,
460 dimensions,
461 range_2: r2,
462 fov_check,
463 visible_points: &mut visible_points,
464 };
465 if visibility_per_sector[0] {
466 scanner.scan_N2NW(1, 0., 1.);
467 scanner.scan_N2NE(1, 0., 1.);
468 }
469
470 if visibility_per_sector[2] {
471 scanner.scan_E2NE(1, 0., 1.);
472 scanner.scan_E2SE(1, 0., 1.);
473 }
474
475 if visibility_per_sector[4] {
476 scanner.scan_S2SE(1, 0., 1.);
477 scanner.scan_S2SW(1, 0., 1.);
478 }
479
480 if visibility_per_sector[6] {
481 scanner.scan_W2SW(1, 0., 1.);
482 scanner.scan_W2NW(1, 0., 1.);
483 }
484
485 visible_points
486 .iter()
487 .copied()
488 .filter(|p| fov_check.in_bounds(*p))
489 .collect()
490}
491
492pub fn field_of_view(start: Point, range: i32, fov_check: &dyn Algorithm2D) -> Vec<Point> {
494 field_of_view_set(start, range, fov_check)
495 .into_iter()
496 .collect()
497}