vis.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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 = {
  83. let mut index = 0;
  84. while index < self.shadows.len() {
  85. if self.shadows[index].start >= other.start {
  86. break;
  87. }
  88. index += 1;
  89. }
  90. index
  91. };
  92. // find whether there's an overlapping previous and next
  93. // shadow segment
  94. // we gotta check here because `index` is unsigned; if we just
  95. // subtracted and `index` was 0 then we'd get a panic.
  96. let previous = if index == 0 {
  97. None
  98. } else {
  99. self.shadows
  100. .get(index - 1)
  101. .filter(|sh| sh.end > other.start)
  102. };
  103. let next = if index == self.shadows.len() {
  104. None
  105. } else {
  106. self.shadows.get(index).filter(|sh| sh.start < other.end)
  107. };
  108. match (previous, next) {
  109. // two overlapping segments: join them together
  110. (Some(_), Some(n)) => {
  111. self.shadows[index - 1].end = n.end;
  112. self.shadows.remove(index);
  113. }
  114. // just one overlapping segment: extend the segment in the
  115. // appropriate direction
  116. (None, Some(_)) => {
  117. self.shadows[index].start = other.start;
  118. }
  119. (Some(_), None) => {
  120. self.shadows[index - 1].end = other.end;
  121. }
  122. // no overlapping segments: add this one
  123. (None, None) => {
  124. self.shadows.insert(index, other);
  125. }
  126. }
  127. }
  128. }
  129. pub struct Viewshed<T> {
  130. pub vis: Board<bool>,
  131. blocking: Box<fn(&T) -> bool>,
  132. }
  133. impl<T> Viewshed<T> {
  134. pub fn create(original: &Board<T>, blocking: fn(&T) -> bool) -> Viewshed<T> {
  135. let vis = Board::new_from(original.width(), original.height(), |_, _| false);
  136. let blocking = Box::new(blocking);
  137. Viewshed { vis, blocking }
  138. }
  139. pub fn visibility(&self, coord: impl Into<Coord>) -> bool {
  140. self.vis[coord.into()]
  141. }
  142. pub fn calculate_from(&mut self, board: &Board<T>, coord: Coord) {
  143. for (_, _, elem) in self.vis.iter_mut() {
  144. *elem = false;
  145. }
  146. self.vis[coord] = true;
  147. for octant in ALL_OCTANTS {
  148. self.refresh_octant(*octant, board, coord);
  149. }
  150. }
  151. fn refresh_octant(&mut self, octant: Octant, board: &Board<T>, coord: Coord) {
  152. let mut line = ShadowLine {
  153. shadows: Vec::new(),
  154. };
  155. let mut full_shadow = false;
  156. for row in 1.. {
  157. let pos = coord + octant.translate(row, 0);
  158. if !board.contains(pos) {
  159. break;
  160. }
  161. for col in 0..=row {
  162. let pos = coord + octant.translate(row, col);
  163. if !board.contains(pos) {
  164. break;
  165. }
  166. if full_shadow {
  167. self.vis[pos] = false;
  168. continue;
  169. }
  170. let projection = Shadow::project_from(row, col);
  171. let visible = !line.in_shadow(&projection);
  172. self.vis[pos] = visible;
  173. if visible && (self.blocking)(&board[pos]) {
  174. line.add(projection);
  175. full_shadow = line.is_full_shadow();
  176. }
  177. }
  178. }
  179. }
  180. }
  181. #[cfg(test)]
  182. mod test {
  183. use crate::{Board, Coord, Viewshed};
  184. macro_rules! board_from_vec {
  185. ($w:expr, $h:expr; [$($vec:tt)*]) => {
  186. {
  187. let w = $w;
  188. let h = $h;
  189. let v = vec![$($vec)*];
  190. assert!(v.len() == w * h);
  191. Board::new_from(w, h, |x, y| {
  192. v.get(x + y * w).unwrap().clone()
  193. })
  194. }
  195. }
  196. }
  197. const V: bool = true;
  198. const U: bool = false;
  199. fn assert_same_vis(p: Coord, exp: &Board<bool>, actual: &Board<bool>) {
  200. if exp != actual {
  201. panic!(
  202. "Expected:\n{}\n========\nActual:\n{}\n",
  203. to_vis(p, exp),
  204. to_vis(p, actual)
  205. );
  206. }
  207. }
  208. #[test]
  209. fn add_shadow_line() {
  210. let mut line = super::ShadowLine {
  211. shadows: Vec::new(),
  212. };
  213. assert_eq!(line.shadows.len(), 0);
  214. line.add(super::Shadow::new(0.0, 0.6));
  215. assert_eq!(line.shadows.len(), 1);
  216. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.6));
  217. line.add(super::Shadow::new(0.4, 1.0));
  218. assert_eq!(line.shadows.len(), 1);
  219. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 1.0));
  220. }
  221. #[test]
  222. fn add_shadow_line_after() {
  223. let mut line = super::ShadowLine {
  224. shadows: Vec::new(),
  225. };
  226. assert_eq!(line.shadows.len(), 0);
  227. line.add(super::Shadow::new(0.0, 0.1));
  228. assert_eq!(line.shadows.len(), 1);
  229. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  230. line.add(super::Shadow::new(0.9, 1.0));
  231. assert_eq!(line.shadows.len(), 2);
  232. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  233. assert_eq!(line.shadows[1], super::Shadow::new(0.9, 1.0));
  234. }
  235. #[test]
  236. fn add_shadow_line_before() {
  237. let mut line = super::ShadowLine {
  238. shadows: Vec::new(),
  239. };
  240. assert_eq!(line.shadows.len(), 0);
  241. line.add(super::Shadow::new(0.9, 1.0));
  242. assert_eq!(line.shadows.len(), 1);
  243. assert_eq!(line.shadows[0], super::Shadow::new(0.9, 1.0));
  244. line.add(super::Shadow::new(0.0, 0.1));
  245. assert_eq!(line.shadows.len(), 2);
  246. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  247. assert_eq!(line.shadows[1], super::Shadow::new(0.9, 1.0));
  248. }
  249. #[test]
  250. fn add_shadow_line_several() {
  251. let mut line = super::ShadowLine {
  252. shadows: Vec::new(),
  253. };
  254. assert_eq!(line.shadows.len(), 0);
  255. line.add(super::Shadow::new(0.5, 0.8));
  256. assert_eq!(line.shadows.len(), 1);
  257. line.add(super::Shadow::new(0.0, 0.667));
  258. assert_eq!(line.shadows.len(), 1);
  259. line.add(super::Shadow::new(0.6, 1.0));
  260. assert_eq!(line.shadows.len(), 1);
  261. }
  262. fn to_vis(p: Coord, vis: &super::Board<bool>) -> String {
  263. let mut buf = String::new();
  264. for y in 0..vis.height() {
  265. for x in 0..vis.width() {
  266. if p.x == x && p.y == y {
  267. buf.push('@')
  268. } else {
  269. buf.push(match vis[(x, y)] {
  270. true => '#',
  271. false => '.',
  272. })
  273. }
  274. }
  275. buf.push('\n')
  276. }
  277. buf
  278. }
  279. #[test]
  280. fn simple_room_visibility() {
  281. let b: Board<isize> = board_from_vec![
  282. 5,5;
  283. [
  284. 0, 0, 0, 0, 0,
  285. 0, 1, 1, 1, 0,
  286. 0, 1, 0, 1, 0,
  287. 0, 1, 1, 1, 0,
  288. 0, 0, 0, 0, 0,
  289. ]
  290. ];
  291. let mut v = Viewshed::create(&b, |n| *n == 1);
  292. let p = Coord::new(2, 2);
  293. v.calculate_from(&b, p);
  294. let exp: Board<bool> = board_from_vec![
  295. 5,5;
  296. [
  297. U, U, U, U, U,
  298. U, V, V, V, U,
  299. U, V, V, V, U,
  300. U, V, V, V, U,
  301. U, U, U, U, U,
  302. ]
  303. ];
  304. assert_same_vis(p, &exp, &v.vis);
  305. }
  306. #[test]
  307. fn big_room_visibility() {
  308. let b: Board<isize> = board_from_vec![
  309. 7,7;
  310. [
  311. 0, 0, 0, 0, 0, 0, 0,
  312. 0, 1, 1, 1, 1, 1, 0,
  313. 0, 1, 0, 0, 0, 1, 0,
  314. 0, 1, 0, 0, 0, 1, 0,
  315. 0, 1, 0, 0, 0, 1, 0,
  316. 0, 1, 1, 1, 1, 1, 0,
  317. 0, 0, 0, 0, 0, 0, 0,
  318. ]
  319. ];
  320. let mut v = Viewshed::create(&b, |n| *n == 1);
  321. let p = Coord::new(3, 3);
  322. v.calculate_from(&b, p);
  323. let exp: Board<bool> = board_from_vec![
  324. 7,7;
  325. [
  326. U, U, U, U, U, U, U,
  327. U, V, V, V, V, V, U,
  328. U, V, V, V, V, V, U,
  329. U, V, V, V, V, V, U,
  330. U, V, V, V, V, V, U,
  331. U, V, V, V, V, V, U,
  332. U, U, U, U, U, U, U,
  333. ]
  334. ];
  335. assert_same_vis(p, &exp, &v.vis);
  336. }
  337. #[test]
  338. fn corridor_visibility() {
  339. let b: Board<isize> = board_from_vec![
  340. 13,7;
  341. [
  342. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  343. 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
  344. 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0,
  345. 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
  346. 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0,
  347. 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
  348. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  349. ]
  350. ];
  351. let mut v = Viewshed::create(&b, |n| *n == 1);
  352. let p = Coord::new(3, 3);
  353. v.calculate_from(&b, p);
  354. let exp: Board<bool> = board_from_vec![
  355. 13,7;
  356. [
  357. U, U, U, U, U, U, U, U, U, U, U, U, U,
  358. U, V, V, V, V, V, U, U, U, U, U, U, U,
  359. U, V, V, V, V, V, V, V, V, V, V, V, U,
  360. U, V, V, V, V, V, V, V, V, V, V, V, U,
  361. U, V, V, V, V, V, V, V, V, V, V, V, U,
  362. U, V, V, V, V, V, U, U, U, U, U, U, U,
  363. U, U, U, U, U, U, U, U, U, U, U, U, U,
  364. ]
  365. ];
  366. assert_same_vis(p, &exp, &v.vis);
  367. }
  368. #[test]
  369. fn last_room_visible() {
  370. let b: Board<isize> = board_from_vec![
  371. 7,5;
  372. [
  373. 0, 0, 0, 0, 0, 0, 0,
  374. 0, 1, 1, 1, 1, 1, 0,
  375. 0, 1, 0, 1, 0, 1, 0,
  376. 0, 1, 1, 1, 1, 1, 0,
  377. 0, 0, 0, 0, 0, 0, 0,
  378. ]
  379. ];
  380. let mut v = Viewshed::create(&b, |n| *n == 1);
  381. let p = Coord::new(2, 2);
  382. v.calculate_from(&b, p);
  383. let exp: Board<bool> = board_from_vec![
  384. 7,5;
  385. [
  386. U, U, U, U, U, U, U,
  387. U, V, V, V, U, U, U,
  388. U, V, V, V, U, U, U,
  389. U, V, V, V, U, U, U,
  390. U, U, U, U, U, U, U,
  391. ]
  392. ];
  393. assert_same_vis(p, &exp, &v.vis);
  394. let p = Coord::new(4, 2);
  395. v.calculate_from(&b, p);
  396. let exp: Board<bool> = board_from_vec![
  397. 7,5;
  398. [
  399. U, U, U, U, U, U, U,
  400. U, U, U, V, V, V, U,
  401. U, U, U, V, V, V, U,
  402. U, U, U, V, V, V, U,
  403. U, U, U, U, U, U, U,
  404. ]
  405. ];
  406. assert_same_vis(p, &exp, &v.vis);
  407. }
  408. #[test]
  409. fn long_room_visible() {
  410. let b: Board<isize> = board_from_vec![
  411. 9,7;
  412. [
  413. 0, 0, 0, 0, 0, 0, 0, 0, 0,
  414. 0, 1, 1, 1, 1, 1, 1, 1, 0,
  415. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  416. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  417. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  418. 0, 1, 1, 1, 1, 1, 1, 1, 0,
  419. 0, 0, 0, 0, 0, 0, 0, 0, 0,
  420. ]
  421. ];
  422. let mut v = Viewshed::create(&b, |n| *n == 1);
  423. let p = Coord::new(2, 2);
  424. v.calculate_from(&b, p);
  425. let exp: Board<bool> = board_from_vec![
  426. 9,7;
  427. [
  428. U, U, U, U, U, U, U, U, U,
  429. U, V, V, V, V, V, V, V, U,
  430. U, V, V, V, V, V, V, V, U,
  431. U, V, V, V, V, V, V, V, U,
  432. U, V, V, V, V, V, V, V, U,
  433. U, V, V, V, V, V, V, V, U,
  434. U, U, U, U, U, U, U, U, U,
  435. ]
  436. ];
  437. assert_same_vis(p, &exp, &v.vis);
  438. }
  439. }