vis.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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(), |_, _| {
  136. false
  137. });
  138. let blocking = Box::new(blocking);
  139. Viewshed { vis, blocking }
  140. }
  141. pub fn visibility(&self, coord: impl Into<Coord>) -> bool {
  142. self.vis[coord.into()]
  143. }
  144. pub fn calculate_from(&mut self, board: &Board<T>, coord: Coord) {
  145. for (_, _, elem) in self.vis.iter_mut() {
  146. *elem = false;
  147. }
  148. self.vis[coord] = true;
  149. for octant in ALL_OCTANTS {
  150. self.refresh_octant(*octant, board, coord);
  151. }
  152. }
  153. fn refresh_octant(&mut self, octant: Octant, board: &Board<T>, coord: Coord) {
  154. let mut line = ShadowLine {
  155. shadows: Vec::new(),
  156. };
  157. let mut full_shadow = false;
  158. for row in 1.. {
  159. let pos = coord + octant.translate(row, 0);
  160. if !board.contains(pos) {
  161. break;
  162. }
  163. for col in 0..=row {
  164. let pos = coord + octant.translate(row, col);
  165. if !board.contains(pos) {
  166. break;
  167. }
  168. if full_shadow {
  169. self.vis[pos] = false;
  170. continue;
  171. }
  172. let projection = Shadow::project_from(row, col);
  173. let visible = !line.in_shadow(&projection);
  174. self.vis[pos] = visible;
  175. if visible && (self.blocking)(&board[pos]) {
  176. line.add(projection);
  177. full_shadow = line.is_full_shadow();
  178. }
  179. }
  180. }
  181. }
  182. }
  183. #[cfg(test)]
  184. mod test {
  185. use crate::{Board, Coord, Viewshed};
  186. macro_rules! board_from_vec {
  187. ($w:expr, $h:expr; [$($vec:tt)*]) => {
  188. {
  189. let w = $w;
  190. let h = $h;
  191. let v = vec![$($vec)*];
  192. assert!(v.len() == w * h);
  193. Board::new_from(w, h, |x, y| {
  194. v.get(x + y * w).unwrap().clone()
  195. })
  196. }
  197. }
  198. }
  199. const V: bool = true;
  200. const U: bool = false;
  201. fn assert_same_vis(exp: &Board<bool>, actual: &Board<bool>) {
  202. if exp != actual {
  203. panic!("Expected:\n{}\n========\nActual:\n{}\n", to_vis(exp), to_vis(actual));
  204. }
  205. }
  206. #[test]
  207. fn add_shadow_line() {
  208. let mut line = super::ShadowLine {
  209. shadows: Vec::new(),
  210. };
  211. assert_eq!(line.shadows.len(), 0);
  212. line.add(super::Shadow::new(0.0, 0.6));
  213. assert_eq!(line.shadows.len(), 1);
  214. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.6));
  215. line.add(super::Shadow::new(0.4, 1.0));
  216. assert_eq!(line.shadows.len(), 1);
  217. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 1.0));
  218. }
  219. #[test]
  220. fn add_shadow_line_after() {
  221. let mut line = super::ShadowLine {
  222. shadows: Vec::new(),
  223. };
  224. assert_eq!(line.shadows.len(), 0);
  225. line.add(super::Shadow::new(0.0, 0.1));
  226. assert_eq!(line.shadows.len(), 1);
  227. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  228. line.add(super::Shadow::new(0.9, 1.0));
  229. assert_eq!(line.shadows.len(), 2);
  230. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  231. assert_eq!(line.shadows[1], super::Shadow::new(0.9, 1.0));
  232. }
  233. #[test]
  234. fn add_shadow_line_before() {
  235. let mut line = super::ShadowLine {
  236. shadows: Vec::new(),
  237. };
  238. assert_eq!(line.shadows.len(), 0);
  239. line.add(super::Shadow::new(0.9, 1.0));
  240. assert_eq!(line.shadows.len(), 1);
  241. assert_eq!(line.shadows[0], super::Shadow::new(0.9, 1.0));
  242. line.add(super::Shadow::new(0.0, 0.1));
  243. assert_eq!(line.shadows.len(), 2);
  244. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.1));
  245. assert_eq!(line.shadows[1], super::Shadow::new(0.9, 1.0));
  246. }
  247. fn to_vis(vis: &super::Board<bool>) -> String {
  248. let mut buf = String::new();
  249. for y in 0..vis.height() {
  250. for x in 0..vis.width() {
  251. buf.push(match vis[(x, y)] {
  252. true => '#',
  253. false => '.',
  254. })
  255. }
  256. buf.push('\n')
  257. }
  258. buf
  259. }
  260. #[test]
  261. fn simple_room_visibility() {
  262. let b: Board<isize> = board_from_vec![
  263. 5,5;
  264. [
  265. 0, 0, 0, 0, 0,
  266. 0, 1, 1, 1, 0,
  267. 0, 1, 0, 1, 0,
  268. 0, 1, 1, 1, 0,
  269. 0, 0, 0, 0, 0,
  270. ]
  271. ];
  272. let mut v = Viewshed::create(&b, |n| *n == 1);
  273. v.calculate_from(&b, Coord::new(2, 2));
  274. let exp: Board<bool> = board_from_vec![
  275. 5,5;
  276. [
  277. U, U, U, U, U,
  278. U, V, V, V, U,
  279. U, V, V, V, U,
  280. U, V, V, V, U,
  281. U, U, U, U, U,
  282. ]
  283. ];
  284. assert_same_vis(&exp, &v.vis);
  285. }
  286. #[test]
  287. fn big_room_visibility() {
  288. let b: Board<isize> = board_from_vec![
  289. 7,7;
  290. [
  291. 0, 0, 0, 0, 0, 0, 0,
  292. 0, 1, 1, 1, 1, 1, 0,
  293. 0, 1, 0, 0, 0, 1, 0,
  294. 0, 1, 0, 0, 0, 1, 0,
  295. 0, 1, 0, 0, 0, 1, 0,
  296. 0, 1, 1, 1, 1, 1, 0,
  297. 0, 0, 0, 0, 0, 0, 0,
  298. ]
  299. ];
  300. let mut v = Viewshed::create(&b, |n| *n == 1);
  301. v.calculate_from(&b, Coord::new(3, 3));
  302. let exp: Board<bool> = board_from_vec![
  303. 7,7;
  304. [
  305. U, U, U, U, U, U, U,
  306. U, V, V, V, V, V, U,
  307. U, V, V, V, V, V, U,
  308. U, V, V, V, V, V, U,
  309. U, V, V, V, V, V, U,
  310. U, V, V, V, V, V, U,
  311. U, U, U, U, U, U, U,
  312. ]
  313. ];
  314. assert_same_vis(&exp, &v.vis);
  315. }
  316. #[test]
  317. fn corridor_visibility() {
  318. let b: Board<isize> = board_from_vec![
  319. 13,7;
  320. [
  321. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  322. 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
  323. 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0,
  324. 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
  325. 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0,
  326. 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
  327. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  328. ]
  329. ];
  330. let mut v = Viewshed::create(&b, |n| *n == 1);
  331. v.calculate_from(&b, Coord::new(3, 3));
  332. let exp: Board<bool> = board_from_vec![
  333. 13,7;
  334. [
  335. U, U, U, U, U, U, U, U, U, U, U, U, U,
  336. U, V, V, V, V, V, U, U, U, U, U, U, U,
  337. U, V, V, V, V, V, V, V, V, V, V, V, U,
  338. U, V, V, V, V, V, V, V, V, V, V, V, U,
  339. U, V, V, V, V, V, V, V, V, V, V, V, U,
  340. U, V, V, V, V, V, U, U, U, U, U, U, U,
  341. U, U, U, U, U, U, U, U, U, U, U, U, U,
  342. ]
  343. ];
  344. assert_same_vis(&exp, &v.vis);
  345. }
  346. #[test]
  347. fn last_room_visible() {
  348. let b: Board<isize> = board_from_vec![
  349. 7,5;
  350. [
  351. 0, 0, 0, 0, 0, 0, 0,
  352. 0, 1, 1, 1, 1, 1, 0,
  353. 0, 1, 0, 1, 0, 1, 0,
  354. 0, 1, 1, 1, 1, 1, 0,
  355. 0, 0, 0, 0, 0, 0, 0,
  356. ]
  357. ];
  358. let mut v = Viewshed::create(&b, |n| *n == 1);
  359. v.calculate_from(&b, Coord::new(2, 2));
  360. let exp: Board<bool> = board_from_vec![
  361. 7,5;
  362. [
  363. U, U, U, U, U, U, U,
  364. U, V, V, V, U, U, U,
  365. U, V, V, V, U, U, U,
  366. U, V, V, V, U, U, U,
  367. U, U, U, U, U, U, U,
  368. ]
  369. ];
  370. assert_same_vis(&exp, &v.vis);
  371. v.calculate_from(&b, Coord::new(4, 2));
  372. let exp: Board<bool> = board_from_vec![
  373. 7,5;
  374. [
  375. U, U, U, U, U, U, U,
  376. U, U, U, V, V, V, U,
  377. U, U, U, V, V, V, U,
  378. U, U, U, V, V, V, U,
  379. U, U, U, U, U, U, U,
  380. ]
  381. ];
  382. assert_same_vis(&exp, &v.vis);
  383. }
  384. #[test]
  385. fn long_room_visible() {
  386. let b: Board<isize> = board_from_vec![
  387. 9,7;
  388. [
  389. 0, 0, 0, 0, 0, 0, 0, 0, 0,
  390. 0, 1, 1, 1, 1, 1, 1, 1, 0,
  391. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  392. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  393. 0, 1, 0, 0, 0, 0, 0, 1, 0,
  394. 0, 1, 1, 1, 1, 1, 1, 1, 0,
  395. 0, 0, 0, 0, 0, 0, 0, 0, 0,
  396. ]
  397. ];
  398. let mut v = Viewshed::create(&b, |n| *n == 1);
  399. v.calculate_from(&b, Coord::new(2, 2));
  400. let exp: Board<bool> = board_from_vec![
  401. 9,7;
  402. [
  403. U, U, U, U, U, U, U, U, U,
  404. U, V, V, V, V, V, V, V, U,
  405. U, V, V, V, V, V, V, V, U,
  406. U, V, V, V, V, V, V, V, U,
  407. U, V, V, V, V, V, V, V, U,
  408. U, V, V, V, V, V, V, V, U,
  409. U, U, U, U, U, U, U, U, U,
  410. ]
  411. ];
  412. assert_same_vis(&exp, &v.vis);
  413. }
  414. }