vis.rs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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. /// In many roguelikes, there are three possible visibility states:
  6. /// completely unseen (represented as undrawn black squares), actively
  7. /// visible (represented as brightly-drawn squares with visible
  8. /// monsters), and discovered-but-currently-occluded (represented as
  9. /// dim squares without drawn monsters).
  10. #[derive(Debug, PartialEq, Eq, Clone, Copy)]
  11. pub enum Visibility {
  12. /// The square is in view of the current POV
  13. Visible,
  14. /// The square has been viewed before but is not currently visible
  15. Seen,
  16. /// The square has not been viewed before
  17. Unseen,
  18. }
  19. /// The algorithm as described uses a number from 0 to 8 for
  20. /// this. This is rarely used and doesn't get us much safety, but it
  21. /// felt better to me to have it be a proper enum.
  22. #[derive(Copy, Clone)]
  23. enum Octant {
  24. NE,
  25. EN,
  26. ES,
  27. SE,
  28. SW,
  29. WS,
  30. WN,
  31. NW,
  32. }
  33. const ALL_OCTANTS: &[Octant] = &[
  34. Octant::NE,
  35. Octant::EN,
  36. Octant::ES,
  37. Octant::SE,
  38. Octant::SW,
  39. Octant::WS,
  40. Octant::WN,
  41. Octant::NW,
  42. ];
  43. impl Octant {
  44. fn translate(&self, column: usize, row: usize) -> (isize, isize) {
  45. let x = column as isize;
  46. let y = row as isize;
  47. match &self {
  48. Octant::NE => (x, -y),
  49. Octant::EN => (y, -x),
  50. Octant::ES => (y, x),
  51. Octant::SE => (x, y),
  52. Octant::SW => (-x, y),
  53. Octant::WS => (-y, x),
  54. Octant::WN => (-y, -x),
  55. Octant::NW => (-x, -y),
  56. }
  57. }
  58. }
  59. /// A shadow represents a single occluded section along an octant. We
  60. /// maintain the invariant that both `start` and `end` in the
  61. /// inclusive range `[0.0, 1.0]` and also that `start < end`.
  62. #[derive(Debug, PartialEq, Clone, Copy)]
  63. struct Shadow {
  64. start: f32,
  65. end: f32,
  66. }
  67. impl Shadow {
  68. fn new(start: f32, end: f32) -> Shadow {
  69. Shadow { start, end }
  70. }
  71. fn project_from(coord: Coord) -> Shadow {
  72. let Coord { x, y } = coord;
  73. let top_left = y as f32 / (x as f32 + 2.0);
  74. let bottom_right = (y as f32 + 1.0) / (x as f32 + 1.0);
  75. Shadow::new(top_left, bottom_right)
  76. }
  77. fn contains(&self, other: &Shadow) -> bool {
  78. self.start <= other.start && self.end >= other.end
  79. }
  80. }
  81. #[derive(Debug, PartialEq)]
  82. struct ShadowLine {
  83. shadows: Vec<Shadow>,
  84. }
  85. impl ShadowLine {
  86. fn in_shadow(&self, other: &Shadow) -> bool {
  87. self.shadows.iter().any(|sh| sh.contains(other))
  88. }
  89. fn is_full_shadow(&self) -> bool {
  90. self.shadows.len() == 1 && self.shadows[0].start.eq(&0.0) && self.shadows[0].end.eq(&1.0)
  91. }
  92. fn add(&mut self, other: Shadow) {
  93. let index = {
  94. let mut index = 0;
  95. for (i, shadow) in self.shadows.iter().enumerate() {
  96. if shadow.start >= other.start {
  97. index = i;
  98. break;
  99. }
  100. }
  101. index
  102. };
  103. // find whether there's an overlapping previous and next
  104. // shadow segment
  105. // we gotta check here because `index` is unsigned; if we just
  106. // subtracted and `index` was 0 then we'd get a panic.
  107. let previous = if index == 0 {
  108. None
  109. } else {
  110. self.shadows
  111. .get(index - 1)
  112. .filter(|sh| sh.end > other.start)
  113. };
  114. let next = self.shadows.get(index).filter(|sh| sh.start < other.end);
  115. match (previous, next) {
  116. // two overlapping segments: join them together
  117. (Some(_), Some(n)) => {
  118. self.shadows[index - 1].end = n.end;
  119. self.shadows.remove(index);
  120. }
  121. // just one overlapping segment: extend the segment in the
  122. // appropriate direction
  123. (None, Some(_)) => {
  124. self.shadows[index].end = other.end;
  125. }
  126. (Some(_), None) => {
  127. self.shadows[index - 1].start = other.start;
  128. }
  129. // no overlapping segments: add this one
  130. (None, None) => {
  131. self.shadows.insert(index, other);
  132. }
  133. }
  134. }
  135. }
  136. pub struct Viewshed<T> {
  137. pub vis: Board<Visibility>,
  138. blocking: Box<fn(&T) -> bool>,
  139. }
  140. impl<T> Viewshed<T> {
  141. pub fn create(original: &Board<T>, blocking: fn(&T) -> bool) -> Viewshed<T> {
  142. let vis = Board::new_from(original.width(), original.height(), |_, _| {
  143. Visibility::Unseen
  144. });
  145. let blocking = Box::new(blocking);
  146. Viewshed { vis, blocking }
  147. }
  148. pub fn calculate_from(&mut self, board: &Board<T>, coord: Coord) {
  149. self.set_visibility(coord, true);
  150. for octant in ALL_OCTANTS {
  151. self.refresh_octant(*octant, board, coord);
  152. }
  153. }
  154. fn refresh_octant(&mut self, octant: Octant, board: &Board<T>, coord: Coord) {
  155. let mut line = ShadowLine {
  156. shadows: Vec::new(),
  157. };
  158. let mut full_shadow = false;
  159. for row in 1.. {
  160. let pos = coord + octant.translate(row, 0);
  161. if !board.contains(pos) {
  162. break;
  163. }
  164. for col in 0..=row {
  165. let pos = coord + octant.translate(row, col);
  166. if !board.contains(pos) {
  167. break;
  168. }
  169. if full_shadow {
  170. self.set_visibility(pos, false);
  171. continue;
  172. }
  173. let projection = Shadow::project_from(Coord::new(row, col));
  174. let visible = !line.in_shadow(&projection);
  175. self.set_visibility(pos, visible);
  176. if visible && (self.blocking)(&board[pos]) {
  177. line.add(projection);
  178. full_shadow = line.is_full_shadow();
  179. }
  180. }
  181. }
  182. }
  183. fn set_visibility(&mut self, coord: Coord, visible: bool) {
  184. if visible {
  185. self.vis[coord] = Visibility::Visible;
  186. } else if self.vis[coord] == Visibility::Unseen {
  187. self.vis[coord] = Visibility::Unseen
  188. } else {
  189. self.vis[coord] = Visibility::Seen
  190. }
  191. }
  192. }
  193. #[cfg(test)]
  194. mod test {
  195. use crate::{Board, Coord, Viewshed, Visibility};
  196. macro_rules! board_from_vec {
  197. ($w:expr, $h:expr; [$($vec:tt)*]) => {
  198. {
  199. let w = $w;
  200. let h = $h;
  201. let v = vec![$($vec)*];
  202. assert!(v.len() == w * h);
  203. Board::new_from(w, h, |x, y| {
  204. v.get(x + y * w).unwrap().clone()
  205. })
  206. }
  207. }
  208. }
  209. const V: Visibility = Visibility::Visible;
  210. const U: Visibility = Visibility::Unseen;
  211. const S: Visibility = Visibility::Seen;
  212. #[test]
  213. fn add_shadow_line() {
  214. let mut line = super::ShadowLine {
  215. shadows: Vec::new(),
  216. };
  217. assert_eq!(line.shadows.len(), 0);
  218. line.add(super::Shadow::new(0.0, 0.6));
  219. assert_eq!(line.shadows.len(), 1);
  220. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 0.6));
  221. line.add(super::Shadow::new(0.4, 1.0));
  222. assert_eq!(line.shadows.len(), 1);
  223. assert_eq!(line.shadows[0], super::Shadow::new(0.0, 1.0));
  224. }
  225. fn to_vis(vis: &super::Board<super::Visibility>) -> String {
  226. let mut buf = String::new();
  227. for x in 0..vis.width() {
  228. for y in 0..vis.height() {
  229. buf.push(match vis[(x, y)] {
  230. V => '.',
  231. S => '#',
  232. U => ' ',
  233. })
  234. }
  235. buf.push('\n')
  236. }
  237. buf
  238. }
  239. #[test]
  240. fn simple_room_visibility() {
  241. let b: Board<isize> = board_from_vec![
  242. 5,5;
  243. [
  244. 0, 0, 0, 0, 0,
  245. 0, 1, 1, 1, 0,
  246. 0, 1, 0, 1, 0,
  247. 0, 1, 1, 1, 0,
  248. 0, 0, 0, 0, 0,
  249. ]
  250. ];
  251. let mut v = Viewshed::create(&b, |n| *n == 1);
  252. v.calculate_from(&b, Coord::new(2, 2));
  253. let exp: Board<Visibility> = board_from_vec![
  254. 5,5;
  255. [
  256. U, U, U, U, U,
  257. U, V, V, V, U,
  258. U, V, V, V, U,
  259. U, V, V, V, U,
  260. U, U, U, U, U,
  261. ]
  262. ];
  263. assert_eq!(to_vis(&v.vis), to_vis(&exp));
  264. }
  265. #[test]
  266. fn last_room_visible() {
  267. let b: Board<isize> = board_from_vec![
  268. 7,5;
  269. [
  270. 0, 0, 0, 0, 0, 0, 0,
  271. 0, 1, 1, 1, 1, 1, 0,
  272. 0, 1, 0, 1, 0, 1, 0,
  273. 0, 1, 1, 1, 1, 1, 0,
  274. 0, 0, 0, 0, 0, 0, 0,
  275. ]
  276. ];
  277. let mut v = Viewshed::create(&b, |n| *n == 1);
  278. v.calculate_from(&b, Coord::new(2, 2));
  279. let exp: Board<Visibility> = board_from_vec![
  280. 7,5;
  281. [
  282. U, U, U, U, U, U, U,
  283. U, V, V, V, U, U, U,
  284. U, V, V, V, U, U, U,
  285. U, V, V, V, U, U, U,
  286. U, U, U, U, U, U, U,
  287. ]
  288. ];
  289. assert_eq!(v.vis, exp);
  290. v.calculate_from(&b, Coord::new(4, 2));
  291. let exp: Board<Visibility> = board_from_vec![
  292. 7,5;
  293. [
  294. U, U, U, U, U, U, U,
  295. U, S, S, V, V, V, U,
  296. U, S, S, V, V, V, U,
  297. U, S, S, V, V, V, U,
  298. U, U, U, U, U, U, U,
  299. ]
  300. ];
  301. assert_eq!(v.vis, exp);
  302. }
  303. }