vis.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. use crate::{Board, Coord};
  2. // This includes an implementation of the visibility algorithm
  3. // described at
  4. // https://journal.stuffwithstuff.com/2015/09/07/what-the-hero-sees/
  5. /// The algorithm as described uses a number from 0 to 8 for
  6. /// this. This is rarely used and doesn't get us much safety, but it
  7. /// felt better to me to have it be a proper enum.
  8. #[derive(Debug, Copy, Clone)]
  9. enum Octant {
  10. NE,
  11. EN,
  12. ES,
  13. SE,
  14. SW,
  15. WS,
  16. WN,
  17. NW,
  18. }
  19. const ALL_OCTANTS: &[Octant] = &[
  20. Octant::NE,
  21. Octant::EN,
  22. Octant::ES,
  23. Octant::SE,
  24. Octant::SW,
  25. Octant::WS,
  26. Octant::WN,
  27. Octant::NW,
  28. ];
  29. impl Octant {
  30. fn translate(&self, column: usize, row: usize) -> (isize, isize) {
  31. let x = column as isize;
  32. let y = row as isize;
  33. match &self {
  34. Octant::NE => (x, -y),
  35. Octant::EN => (y, -x),
  36. Octant::ES => (y, x),
  37. Octant::SE => (x, y),
  38. Octant::SW => (-x, y),
  39. Octant::WS => (-y, x),
  40. Octant::WN => (-y, -x),
  41. Octant::NW => (-x, -y),
  42. }
  43. }
  44. }
  45. /// A shadow represents a single occluded section along an octant. We
  46. /// maintain the invariant that both `start` and `end` in the
  47. /// inclusive range `[0.0, 1.0]` and also that `start < end`.
  48. #[derive(Debug, PartialEq, Clone, Copy)]
  49. struct Shadow {
  50. start: f32,
  51. end: f32,
  52. }
  53. impl Shadow {
  54. fn new(a: f32, b: f32) -> Shadow {
  55. let start = a.min(b);
  56. let end = a.max(b);
  57. Shadow { start, end }
  58. }
  59. fn project_from(row: usize, col: usize) -> Shadow {
  60. let row = row as f32;
  61. let col = col as f32;
  62. let top_left = col / (row + 2.0);
  63. let bottom_right = (col + 1.0) / (row + 1.0);
  64. Shadow::new(top_left, bottom_right)
  65. }
  66. fn contains(&self, other: &Shadow) -> bool {
  67. self.start <= other.start && self.end >= other.end
  68. }
  69. }
  70. #[derive(Debug, PartialEq)]
  71. struct ShadowLine {
  72. shadows: Vec<Shadow>,
  73. }
  74. impl ShadowLine {
  75. fn in_shadow(&self, other: &Shadow) -> bool {
  76. self.shadows.iter().any(|sh| sh.contains(other))
  77. }
  78. fn is_full_shadow(&self) -> bool {
  79. self.shadows.len() == 1 && self.shadows[0].start.eq(&0.0) && self.shadows[0].end.eq(&1.0)
  80. }
  81. fn add(&mut self, other: Shadow) {
  82. let index = self
  83. .shadows
  84. .iter()
  85. .position(|sh| sh.start >= other.start)
  86. .unwrap_or(self.shadows.len());
  87. // find whether there's an overlapping previous and next
  88. // shadow segment
  89. // we gotta check here because `index` is unsigned; if we just
  90. // subtracted and `index` was 0 then we'd get a panic.
  91. let previous = if index == 0 {
  92. None
  93. } else {
  94. self.shadows
  95. .get(index - 1)
  96. .filter(|sh| sh.end > other.start)
  97. };
  98. let next = self.shadows.get(index).filter(|sh| sh.start < other.end);
  99. match (previous, next) {
  100. // 1 2 3 4 5 6 7 1 2 3 4 5 6 7
  101. // [prev] [next] => [prev.......]
  102. // [other]
  103. //
  104. // two overlapping segments: join them together,
  105. // specifically extending the previous one and deleting
  106. // the second
  107. (Some(_), Some(n)) => {
  108. self.shadows[index - 1].end = n.end;
  109. self.shadows.remove(index);
  110. }
  111. // 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9
  112. // [prev] [next] => [prev] [next...]
  113. // [other]
  114. //
  115. // just overlapping a later segment: pull the later
  116. // segment's start point earlier
  117. (None, Some(_)) => {
  118. self.shadows[index].start = other.start;
  119. }
  120. // 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9
  121. // [prev] [next] => [prev...] [next]
  122. // [other]
  123. //
  124. // just overlapping an earlier segment: pull the earlier
  125. // segment's end point later
  126. (Some(_), None) => {
  127. self.shadows[index - 1].end = other.end;
  128. }
  129. // 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
  130. // [p] [n] => [p] [other] [n]
  131. // [other]
  132. // no overlapping segments: just add this one in between
  133. (None, None) => {
  134. self.shadows.insert(index, other);
  135. }
  136. }
  137. }
  138. }
  139. pub struct Viewshed<T> {
  140. pub vis: Board<bool>,
  141. blocking: Box<fn(&T) -> bool>,
  142. pub range: Option<usize>,
  143. }
  144. impl<T> Viewshed<T> {
  145. pub fn create(original: &Board<T>, blocking: fn(&T) -> bool) -> Viewshed<T> {
  146. let vis = Board::new_from(original.width(), original.height(), |_, _| false);
  147. let blocking = Box::new(blocking);
  148. let range = None;
  149. Viewshed {
  150. vis,
  151. blocking,
  152. range,
  153. }
  154. }
  155. pub fn visibility(&self, coord: impl Into<Coord>) -> bool {
  156. self.vis[coord.into()]
  157. }
  158. pub fn calculate_from(&mut self, board: &Board<T>, coord: Coord) {
  159. for (_, _, elem) in self.vis.iter_mut() {
  160. *elem = false;
  161. }
  162. self.vis[coord] = true;
  163. for octant in ALL_OCTANTS {
  164. self.refresh_octant(*octant, board, coord);
  165. }
  166. }
  167. fn refresh_octant(&mut self, octant: Octant, board: &Board<T>, coord: Coord) {
  168. let mut line = ShadowLine {
  169. shadows: Vec::new(),
  170. };
  171. let mut full_shadow = false;
  172. for row in 1.. {
  173. let pos = coord + octant.translate(row, 0);
  174. if !board.contains(pos) {
  175. break;
  176. }
  177. for col in 0..=row {
  178. let pos = coord + octant.translate(row, col);
  179. if !board.contains(pos) {
  180. break;
  181. }
  182. if full_shadow {
  183. self.vis[pos] = false;
  184. continue;
  185. }
  186. let projection = Shadow::project_from(row, col);
  187. let visible = !line.in_shadow(&projection);
  188. self.vis[pos] = visible;
  189. if visible {
  190. let out_of_range = if let Some(r) = self.range {
  191. ((row.pow(2) + col.pow(2)) as f32).sqrt() >= r as f32
  192. } else {
  193. false
  194. };
  195. if out_of_range || (self.blocking)(&board[pos]) {
  196. line.add(projection);
  197. full_shadow = line.is_full_shadow();
  198. }
  199. }
  200. }
  201. }
  202. }
  203. }
  204. #[cfg(test)]
  205. mod test {
  206. use crate::{Board, Coord, Viewshed};
  207. macro_rules! board_from_vec {
  208. ($w:expr, $h:expr; [$($vec:tt)*]) => {
  209. {
  210. let w = $w;
  211. let h = $h;
  212. let v = vec![$($vec)*];
  213. assert!(v.len() == w * h);
  214. Board::new_from(w, h, |x, y| {
  215. v.get(x + y * w).unwrap().clone()
  216. })
  217. }
  218. }
  219. }
  220. const V: bool = true;
  221. const U: bool = false;
  222. fn assert_same_vis(p: Coord, exp: &Board<bool>, actual: &Board<bool>) {
  223. if exp != actual {
  224. panic!(
  225. "Expected:\n{}\n========\nActual:\n{}\n",
  226. to_vis(p, exp),
  227. to_vis(p, actual)
  228. );
  229. }
  230. }
  231. #[test]
  232. fn add_shadow_line() {
  233. let mut line = super::ShadowLine {
  234. shadows: Vec::new(),
  235. };
  236. assert_eq!(line.shadows.len(), 0);
  237. line.add(super::Shadow::new(0.0, 0.6));
  238. assert_eq!(line.shadows.len(), 1);
  239. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.6));
  240. line.add(super::Shadow::new(0.4, 1.0));
  241. assert_eq!(line.shadows.len(), 1);
  242. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 1.0));
  243. }
  244. #[test]
  245. fn add_shadow_line_after() {
  246. let mut line = super::ShadowLine {
  247. shadows: Vec::new(),
  248. };
  249. assert_eq!(line.shadows.len(), 0);
  250. line.add(super::Shadow::new(0.0, 0.1));
  251. assert_eq!(line.shadows.len(), 1);
  252. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  253. line.add(super::Shadow::new(0.9, 1.0));
  254. assert_eq!(line.shadows.len(), 2);
  255. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  256. assert_eq!(line.shadows[1], super::Shadow::new(0.9, 1.0));
  257. }
  258. #[test]
  259. fn add_shadow_line_before() {
  260. let mut line = super::ShadowLine {
  261. shadows: Vec::new(),
  262. };
  263. assert_eq!(line.shadows.len(), 0);
  264. line.add(super::Shadow::new(0.9, 1.0));
  265. assert_eq!(line.shadows.len(), 1);
  266. assert_eq!(line.shadows[0], super::Shadow::new(0.9, 1.0));
  267. line.add(super::Shadow::new(0.0, 0.1));
  268. assert_eq!(line.shadows.len(), 2);
  269. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  270. assert_eq!(line.shadows[1], super::Shadow::new(0.9, 1.0));
  271. }
  272. #[test]
  273. fn add_shadow_line_several() {
  274. let mut line = super::ShadowLine {
  275. shadows: Vec::new(),
  276. };
  277. assert_eq!(line.shadows.len(), 0);
  278. line.add(super::Shadow::new(0.5, 0.8));
  279. assert_eq!(line.shadows.len(), 1);
  280. line.add(super::Shadow::new(0.0, 0.667));
  281. assert_eq!(line.shadows.len(), 1);
  282. line.add(super::Shadow::new(0.6, 1.0));
  283. assert_eq!(line.shadows.len(), 1);
  284. }
  285. fn to_vis(p: Coord, vis: &super::Board<bool>) -> String {
  286. let mut buf = String::new();
  287. for y in 0..vis.height() {
  288. for x in 0..vis.width() {
  289. if p.x == x && p.y == y {
  290. buf.push('@')
  291. } else {
  292. buf.push(match vis[(x, y)] {
  293. true => '#',
  294. false => '.',
  295. })
  296. }
  297. }
  298. buf.push('\n')
  299. }
  300. buf
  301. }
  302. #[test]
  303. fn simple_room_visibility() {
  304. let b: Board<isize> = board_from_vec![
  305. 5,5;
  306. [
  307. 0, 0, 0, 0, 0,
  308. 0, 1, 1, 1, 0,
  309. 0, 1, 0, 1, 0,
  310. 0, 1, 1, 1, 0,
  311. 0, 0, 0, 0, 0,
  312. ]
  313. ];
  314. let mut v = Viewshed::create(&b, |n| *n == 1);
  315. let p = Coord::new(2, 2);
  316. v.calculate_from(&b, p);
  317. let exp: Board<bool> = board_from_vec![
  318. 5,5;
  319. [
  320. U, U, U, U, U,
  321. U, V, V, V, U,
  322. U, V, V, V, U,
  323. U, V, V, V, U,
  324. U, U, U, U, U,
  325. ]
  326. ];
  327. assert_same_vis(p, &exp, &v.vis);
  328. }
  329. #[test]
  330. fn big_room_visibility() {
  331. let b: Board<isize> = board_from_vec![
  332. 7,7;
  333. [
  334. 0, 0, 0, 0, 0, 0, 0,
  335. 0, 1, 1, 1, 1, 1, 0,
  336. 0, 1, 0, 0, 0, 1, 0,
  337. 0, 1, 0, 0, 0, 1, 0,
  338. 0, 1, 0, 0, 0, 1, 0,
  339. 0, 1, 1, 1, 1, 1, 0,
  340. 0, 0, 0, 0, 0, 0, 0,
  341. ]
  342. ];
  343. let mut v = Viewshed::create(&b, |n| *n == 1);
  344. let p = Coord::new(3, 3);
  345. v.calculate_from(&b, p);
  346. let exp: Board<bool> = board_from_vec![
  347. 7,7;
  348. [
  349. U, U, U, U, U, U, U,
  350. U, V, V, V, V, V, U,
  351. U, V, V, V, V, V, U,
  352. U, V, V, V, V, V, U,
  353. U, V, V, V, V, V, U,
  354. U, V, V, V, V, V, U,
  355. U, U, U, U, U, U, U,
  356. ]
  357. ];
  358. assert_same_vis(p, &exp, &v.vis);
  359. }
  360. #[test]
  361. fn corridor_visibility() {
  362. let b: Board<isize> = board_from_vec![
  363. 13,7;
  364. [
  365. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  366. 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
  367. 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0,
  368. 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
  369. 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0,
  370. 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
  371. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  372. ]
  373. ];
  374. let mut v = Viewshed::create(&b, |n| *n == 1);
  375. let p = Coord::new(3, 3);
  376. v.calculate_from(&b, p);
  377. let exp: Board<bool> = board_from_vec![
  378. 13,7;
  379. [
  380. U, U, U, U, U, U, U, U, U, U, U, U, U,
  381. U, V, V, V, V, V, U, U, U, U, U, U, U,
  382. U, V, V, V, V, V, V, V, V, V, V, V, U,
  383. U, V, V, V, V, V, V, V, V, V, V, V, U,
  384. U, V, V, V, V, V, V, V, V, V, V, V, U,
  385. U, V, V, V, V, V, U, U, U, U, U, U, U,
  386. U, U, U, U, U, U, U, U, U, U, U, U, U,
  387. ]
  388. ];
  389. assert_same_vis(p, &exp, &v.vis);
  390. }
  391. #[test]
  392. fn last_room_visible() {
  393. let b: Board<isize> = board_from_vec![
  394. 7,5;
  395. [
  396. 0, 0, 0, 0, 0, 0, 0,
  397. 0, 1, 1, 1, 1, 1, 0,
  398. 0, 1, 0, 1, 0, 1, 0,
  399. 0, 1, 1, 1, 1, 1, 0,
  400. 0, 0, 0, 0, 0, 0, 0,
  401. ]
  402. ];
  403. let mut v = Viewshed::create(&b, |n| *n == 1);
  404. let p = Coord::new(2, 2);
  405. v.calculate_from(&b, p);
  406. let exp: Board<bool> = board_from_vec![
  407. 7,5;
  408. [
  409. U, U, U, U, U, U, U,
  410. U, V, V, V, U, U, U,
  411. U, V, V, V, U, U, U,
  412. U, V, V, V, U, U, U,
  413. U, U, U, U, U, U, U,
  414. ]
  415. ];
  416. assert_same_vis(p, &exp, &v.vis);
  417. let p = Coord::new(4, 2);
  418. v.calculate_from(&b, p);
  419. let exp: Board<bool> = board_from_vec![
  420. 7,5;
  421. [
  422. U, U, U, U, U, U, U,
  423. U, U, U, V, V, V, U,
  424. U, U, U, V, V, V, U,
  425. U, U, U, V, V, V, U,
  426. U, U, U, U, U, U, U,
  427. ]
  428. ];
  429. assert_same_vis(p, &exp, &v.vis);
  430. }
  431. #[test]
  432. fn long_room_visible() {
  433. let b: Board<isize> = board_from_vec![
  434. 9,7;
  435. [
  436. 0, 0, 0, 0, 0, 0, 0, 0, 0,
  437. 0, 1, 1, 1, 1, 1, 1, 1, 0,
  438. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  439. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  440. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  441. 0, 1, 1, 1, 1, 1, 1, 1, 0,
  442. 0, 0, 0, 0, 0, 0, 0, 0, 0,
  443. ]
  444. ];
  445. let mut v = Viewshed::create(&b, |n| *n == 1);
  446. let p = Coord::new(2, 2);
  447. v.calculate_from(&b, p);
  448. let exp: Board<bool> = board_from_vec![
  449. 9,7;
  450. [
  451. U, U, U, U, U, U, U, U, U,
  452. U, V, V, V, V, V, V, V, U,
  453. U, V, V, V, V, V, V, V, U,
  454. U, V, V, V, V, V, V, V, U,
  455. U, V, V, V, V, V, V, V, U,
  456. U, V, V, V, V, V, V, V, U,
  457. U, U, U, U, U, U, U, U, U,
  458. ]
  459. ];
  460. assert_same_vis(p, &exp, &v.vis);
  461. }
  462. }