raycast.rs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. use crate::scene::{ Tile, Scene };
  2. use crate::trig;
  3. use itertools::Itertools;
  4. use shared::consts;
  5. use shared::fp;
  6. use shared::fp::{ ToFixedPoint, FromFixedPoint };
  7. #[derive(Copy, Clone)]
  8. pub struct Intersection {
  9. pub x: i32,
  10. pub y: i32,
  11. pub dist: i32,
  12. pub texture: u32,
  13. pub texture_column: i32,
  14. pub reverse: bool,
  15. }
  16. impl Intersection {
  17. pub fn new(x: i32, y: i32, dist:i32, texture: u32, texture_column: i32, reverse: bool) -> Intersection {
  18. Intersection { x, y, dist, texture, texture_column, reverse }
  19. }
  20. }
  21. struct Ray<'a> {
  22. step_x: i32, // distance to next vertical intersect
  23. step_y: i32, // distance to next horizontal intersect
  24. x: i32, // x coordinate of current ray intersect
  25. y: i32, // y coordinate of current ray intersect
  26. flipped: bool, // should the texture of the encountered surface be rendered backwards
  27. direction: i32, // direction in which the ray is cast
  28. scene: &'a Scene, // the environment in which the ray is being cast
  29. origin_x: i32, // x point of origin of the ray in fixed point representation
  30. origin_y: i32, // y point of origin of the ray in fixed point representation
  31. cast_ray: fn(&mut Self) -> Option<Intersection>, // either cast_horizontal or cast_vertical depending on ray type
  32. check_undefined: fn(&Self) -> bool // either horizontal_is_undefined or vertical_is_undefined depending on ray type
  33. }
  34. impl Ray<'_> {
  35. pub fn horizontal(origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Ray {
  36. let step_x: i32;
  37. let step_y: i32;
  38. let x: i32;
  39. let y: i32;
  40. let flipped: bool;
  41. // determine if looking up or down and find horizontal intersection
  42. if direction > trig::ANGLE_0 && direction < trig::ANGLE_180 { // looking down
  43. step_x = trig::x_step(direction);
  44. step_y = consts::FP_TILE_SIZE;
  45. y = ((origin_y.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE + consts::TILE_SIZE).to_fp();
  46. x = fp::add(origin_x, fp::mul(fp::sub(y, origin_y), trig::itan(direction)));
  47. flipped = true;
  48. } else { // looking up
  49. step_x = trig::x_step(direction);
  50. step_y = -consts::FP_TILE_SIZE;
  51. y = ((origin_y.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE).to_fp();
  52. x = fp::add(origin_x, fp::mul(fp::sub(y, origin_y), trig::itan(direction)));
  53. flipped = false;
  54. }
  55. Ray { step_x, step_y, x, y, flipped, direction, scene, origin_x, origin_y, check_undefined: Ray::horizontal_is_undefined, cast_ray: Ray::cast_horizontal }
  56. }
  57. pub fn vertical(origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Ray {
  58. let step_x: i32; // distance to next vertical intersect
  59. let step_y: i32; // distance to next horizontal intersect
  60. let x: i32; // x coordinate of current ray intersect
  61. let y: i32; // y coordinate of current ray intersect
  62. let flipped: bool;
  63. // determine if looking left or right and find vertical intersection
  64. if direction <= trig::ANGLE_90 || direction > trig::ANGLE_270 { // looking right
  65. step_x = consts::FP_TILE_SIZE;
  66. step_y = trig::y_step(direction);
  67. x = ((origin_x.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE + consts::TILE_SIZE).to_fp();
  68. y = fp::add(origin_y, fp::mul(fp::sub(x, origin_x), trig::tan(direction)));
  69. flipped = false;
  70. } else {
  71. step_x = -consts::FP_TILE_SIZE;
  72. step_y = trig::y_step(direction);
  73. x = (((origin_x.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE)).to_fp();
  74. y = fp::add(origin_y, fp::mul(fp::sub(x, origin_x), trig::tan(direction)));
  75. flipped = true;
  76. };
  77. Ray { step_x, step_y, x, y, flipped, direction, scene, origin_x, origin_y, check_undefined: Ray::vertical_is_undefined, cast_ray: Ray::cast_vertical }
  78. }
  79. pub fn is_undefined(&self) -> bool {
  80. (self.check_undefined)(self)
  81. }
  82. pub fn cast(&mut self) -> Option<Intersection> {
  83. (self.cast_ray)(self)
  84. }
  85. fn horizontal_is_undefined(&self) -> bool {
  86. self.direction == trig::ANGLE_0 || self.direction == trig::ANGLE_180
  87. }
  88. fn vertical_is_undefined(&self) -> bool {
  89. self.direction == trig::ANGLE_90 || self.direction == trig::ANGLE_270
  90. }
  91. fn cast_horizontal(&mut self) -> Option<Intersection> {
  92. let mut result = None;
  93. while !result.is_some() {
  94. let grid_x = fp::div(self.x, consts::FP_TILE_SIZE).to_i32();
  95. let grid_y = fp::div(self.y, consts::FP_TILE_SIZE).to_i32();
  96. match self.scene.y_wall(grid_x, grid_y) {
  97. Tile::Wall(wall) => {
  98. let world_x = self.x.to_i32();
  99. let world_y = self.y.to_i32();
  100. let distance = fp::mul(fp::sub(self.y, self.origin_y), trig::isin(self.direction)).abs();
  101. let texture = wall.texture;
  102. let texture_column = world_x & (consts::TILE_SIZE - 1);
  103. result = Some(Intersection::new(world_x, world_y, distance, texture, texture_column, self.flipped));
  104. },
  105. Tile::OutOfBounds => break,
  106. Tile::Empty => {}
  107. }
  108. self.x = fp::add(self.x, self.step_x);
  109. self.y = fp::add(self.y, self.step_y);
  110. }
  111. result
  112. }
  113. fn cast_vertical(&mut self) -> Option<Intersection> {
  114. let mut result = None;
  115. while !result.is_some() {
  116. let grid_x = fp::div(self.x, consts::FP_TILE_SIZE).to_i32();
  117. let grid_y = fp::div(self.y, consts::FP_TILE_SIZE).to_i32();
  118. match self.scene.x_wall(grid_x, grid_y) {
  119. Tile::Wall(wall) => {
  120. let world_x = self.x.to_i32();
  121. let world_y = self.y.to_i32();
  122. let distance = fp::mul(fp::sub(self.x, self.origin_x), trig::icos(self.direction)).abs();
  123. let texture = wall.texture;
  124. let texture_column = world_y & (consts::TILE_SIZE - 1);
  125. result = Some(Intersection::new(world_x, world_y, distance, texture, texture_column, self.flipped));
  126. },
  127. Tile::OutOfBounds => break,
  128. Tile::Empty => {}
  129. }
  130. self.x = fp::add(self.x, self.step_x);
  131. self.y = fp::add(self.y, self.step_y);
  132. }
  133. result
  134. }
  135. }
  136. impl Iterator for Ray<'_> {
  137. type Item = Intersection;
  138. fn next(&mut self) -> Option<Self::Item> {
  139. self.cast()
  140. }
  141. }
  142. pub struct RayCaster {}
  143. impl RayCaster {
  144. pub fn new() -> RayCaster {
  145. RayCaster {}
  146. }
  147. pub fn find_wall_intersections(&self, origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Vec<Intersection> {
  148. let ray_h = Ray::horizontal(origin_x, origin_y, direction, scene);
  149. let ray_v = Ray::vertical(origin_x, origin_y, direction, scene);
  150. if ray_h.is_undefined() { return ray_v.collect(); }
  151. if ray_v.is_undefined() { return ray_h.collect(); }
  152. vec![ray_h, ray_v].into_iter().kmerge_by(|a, b| a.dist < b.dist).collect()
  153. }
  154. }
  155. #[cfg(test)]
  156. mod test {
  157. use super::*;
  158. use std::fs;
  159. use std::path::Path;
  160. use std::path::PathBuf;
  161. fn load_scene(fname: &PathBuf) -> Result<Scene, Box<dyn std::error::Error>> {
  162. let contents = fs::read_to_string(fname)?;
  163. let json: serde_json::Value = serde_json::from_str(contents.as_str())?;
  164. let scene = Scene::from_json(&json)?;
  165. Ok(scene)
  166. }
  167. #[test]
  168. fn test_facing_directly_right() {
  169. let fname = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("resources").join("test-scene-1.json");
  170. let scene = load_scene(&fname).expect("Failed to load scene for test");
  171. let raycaster = RayCaster::new();
  172. let intersections = raycaster.find_wall_intersections(128.to_fp(), 128.to_fp(), trig::ANGLE_0, &scene);
  173. assert_eq!(1, intersections.len());
  174. }
  175. #[test]
  176. fn test_against_wall() {
  177. let fname = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("resources").join("test-scene-1.json");
  178. let scene = load_scene(&fname).expect("Failed to load scene for test");
  179. let raycaster = RayCaster::new();
  180. let intersections = raycaster.find_wall_intersections(28.to_fp(), 28.to_fp(), trig::ANGLE_270, &scene);
  181. assert_eq!(1, intersections.len());
  182. let intersection = intersections[0];
  183. assert_eq!(28, intersection.dist.to_i32());
  184. }
  185. }