| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- use crate::scene::{ Tile, Scene };
- use crate::trig;
- use itertools::Itertools;
- use shared::consts;
- use shared::fp;
- use shared::fp::{ ToFixedPoint, FromFixedPoint };
- #[derive(Copy, Clone)]
- pub struct Intersection {
- pub x: i32,
- pub y: i32,
- pub dist: i32,
- pub texture: u32,
- pub texture_column: i32,
- pub reverse: bool,
- }
- impl Intersection {
- pub fn new(x: i32, y: i32, dist:i32, texture: u32, texture_column: i32, reverse: bool) -> Intersection {
- Intersection { x, y, dist, texture, texture_column, reverse }
- }
- }
- struct Ray<'a> {
- step_x: i32, // distance to next vertical intersect
- step_y: i32, // distance to next horizontal intersect
- x: i32, // x coordinate of current ray intersect
- y: i32, // y coordinate of current ray intersect
- flipped: bool, // should the texture of the encountered surface be rendered backwards
- direction: i32, // direction in which the ray is cast
- scene: &'a Scene, // the environment in which the ray is being cast
- origin_x: i32, // x point of origin of the ray in fixed point representation
- origin_y: i32, // y point of origin of the ray in fixed point representation
- cast_ray: fn(&mut Self) -> Option<Intersection>, // either cast_horizontal or cast_vertical depending on ray type
- check_undefined: fn(&Self) -> bool // either horizontal_is_undefined or vertical_is_undefined depending on ray type
- }
- impl Ray<'_> {
- pub fn horizontal(origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Ray {
- let step_x: i32;
- let step_y: i32;
- let x: i32;
- let y: i32;
- let flipped: bool;
- // determine if looking up or down and find horizontal intersection
- if direction > trig::ANGLE_0 && direction < trig::ANGLE_180 { // looking down
- step_x = trig::x_step(direction);
- step_y = consts::FP_TILE_SIZE;
- y = ((origin_y.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE + consts::TILE_SIZE).to_fp();
- x = fp::add(origin_x, fp::mul(fp::sub(y, origin_y), trig::itan(direction)));
- flipped = true;
- } else { // looking up
- step_x = trig::x_step(direction);
- step_y = -consts::FP_TILE_SIZE;
- y = ((origin_y.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE).to_fp();
- x = fp::add(origin_x, fp::mul(fp::sub(y, origin_y), trig::itan(direction)));
- flipped = false;
- }
- 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 }
- }
- pub fn vertical(origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Ray {
- let step_x: i32; // distance to next vertical intersect
- let step_y: i32; // distance to next horizontal intersect
- let x: i32; // x coordinate of current ray intersect
- let y: i32; // y coordinate of current ray intersect
- let flipped: bool;
- // determine if looking left or right and find vertical intersection
- if direction <= trig::ANGLE_90 || direction > trig::ANGLE_270 { // looking right
- step_x = consts::FP_TILE_SIZE;
- step_y = trig::y_step(direction);
-
- x = ((origin_x.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE + consts::TILE_SIZE).to_fp();
- y = fp::add(origin_y, fp::mul(fp::sub(x, origin_x), trig::tan(direction)));
-
- flipped = false;
- } else {
- step_x = -consts::FP_TILE_SIZE;
- step_y = trig::y_step(direction);
-
- x = (((origin_x.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE)).to_fp();
- y = fp::add(origin_y, fp::mul(fp::sub(x, origin_x), trig::tan(direction)));
-
- flipped = true;
- };
- 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 }
- }
-
- pub fn is_undefined(&self) -> bool {
- (self.check_undefined)(self)
- }
- pub fn cast(&mut self) -> Option<Intersection> {
- (self.cast_ray)(self)
- }
- fn horizontal_is_undefined(&self) -> bool {
- self.direction == trig::ANGLE_0 || self.direction == trig::ANGLE_180
- }
- fn vertical_is_undefined(&self) -> bool {
- self.direction == trig::ANGLE_90 || self.direction == trig::ANGLE_270
- }
- fn cast_horizontal(&mut self) -> Option<Intersection> {
- let mut result = None;
- while !result.is_some() {
- let grid_x = fp::div(self.x, consts::FP_TILE_SIZE).to_i32();
- let grid_y = fp::div(self.y, consts::FP_TILE_SIZE).to_i32();
-
- match self.scene.y_wall(grid_x, grid_y) {
- Tile::Wall(wall) => {
- let world_x = self.x.to_i32();
- let world_y = self.y.to_i32();
- let distance = fp::mul(fp::sub(self.y, self.origin_y), trig::isin(self.direction)).abs();
- let texture = wall.texture;
- let texture_column = world_x & (consts::TILE_SIZE - 1);
- result = Some(Intersection::new(world_x, world_y, distance, texture, texture_column, self.flipped));
- },
- Tile::OutOfBounds => break,
- Tile::Empty => {}
- }
- self.x = fp::add(self.x, self.step_x);
- self.y = fp::add(self.y, self.step_y);
- }
- result
- }
- fn cast_vertical(&mut self) -> Option<Intersection> {
- let mut result = None;
- while !result.is_some() {
- let grid_x = fp::div(self.x, consts::FP_TILE_SIZE).to_i32();
- let grid_y = fp::div(self.y, consts::FP_TILE_SIZE).to_i32();
- match self.scene.x_wall(grid_x, grid_y) {
- Tile::Wall(wall) => {
- let world_x = self.x.to_i32();
- let world_y = self.y.to_i32();
- let distance = fp::mul(fp::sub(self.x, self.origin_x), trig::icos(self.direction)).abs();
- let texture = wall.texture;
- let texture_column = world_y & (consts::TILE_SIZE - 1);
- result = Some(Intersection::new(world_x, world_y, distance, texture, texture_column, self.flipped));
- },
- Tile::OutOfBounds => break,
- Tile::Empty => {}
- }
- self.x = fp::add(self.x, self.step_x);
- self.y = fp::add(self.y, self.step_y);
- }
- result
- }
- }
- impl Iterator for Ray<'_> {
- type Item = Intersection;
- fn next(&mut self) -> Option<Self::Item> {
- self.cast()
- }
- }
- pub struct RayCaster {}
- impl RayCaster {
- pub fn new() -> RayCaster {
- RayCaster {}
- }
- pub fn find_wall_intersections(&self, origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Vec<Intersection> {
- let ray_h = Ray::horizontal(origin_x, origin_y, direction, scene);
- let ray_v = Ray::vertical(origin_x, origin_y, direction, scene);
- if ray_h.is_undefined() { return ray_v.collect(); }
- if ray_v.is_undefined() { return ray_h.collect(); }
- vec![ray_h, ray_v].into_iter().kmerge_by(|a, b| a.dist < b.dist).collect()
- }
- }
- #[cfg(test)]
- mod test {
- use super::*;
- use std::fs;
- use std::path::Path;
- use std::path::PathBuf;
- fn load_scene(fname: &PathBuf) -> Result<Scene, Box<dyn std::error::Error>> {
- let contents = fs::read_to_string(fname)?;
- let json: serde_json::Value = serde_json::from_str(contents.as_str())?;
- let scene = Scene::from_json(&json)?;
- Ok(scene)
- }
- #[test]
- fn test_facing_directly_right() {
- let fname = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("resources").join("test-scene-1.json");
- let scene = load_scene(&fname).expect("Failed to load scene for test");
- let raycaster = RayCaster::new();
- let intersections = raycaster.find_wall_intersections(128.to_fp(), 128.to_fp(), trig::ANGLE_0, &scene);
- assert_eq!(1, intersections.len());
- }
- #[test]
- fn test_against_wall() {
- let fname = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("resources").join("test-scene-1.json");
- let scene = load_scene(&fname).expect("Failed to load scene for test");
- let raycaster = RayCaster::new();
- let intersections = raycaster.find_wall_intersections(28.to_fp(), 28.to_fp(), trig::ANGLE_270, &scene);
- assert_eq!(1, intersections.len());
- let intersection = intersections[0];
- assert_eq!(28, intersection.dist.to_i32());
- }
- }
|