Ver Fonte

Build broken by attempt to use iterators to optimise raycast

Gary Munnelly há 2 anos atrás
pai
commit
783fd4b454

+ 37 - 4
demo/src/lib.rs

@@ -1,29 +1,62 @@
 use fourteen_screws::{ Camera, Scene, Renderer };
+use fourteen_screws::trig;
+
 use serde_json;
 use wasm_bindgen::prelude::*;
 extern crate web_sys;
 
+mod player;
+
+const PLAYER_MARGIN: i32     = 28;
+const PLAYER_MOVE_SPEED: i32 = 8;
+const PLAYER_TURN_SPEED: i32 = trig::ANGLE_5;
+
 #[wasm_bindgen]
 pub struct FourteenScrewsDemo {
 	scene:  Scene,
-	player: Camera,
+	player: player::Player,
 	renderer: Renderer,
 }
 
 #[wasm_bindgen]
 impl FourteenScrewsDemo {
+	pub fn player_forward(&mut self) {
+		self.player.forward(&self.scene);
+	}
+
+	pub fn player_back(&mut self) {
+		self.player.back(&self.scene);
+	}
+
+	pub fn player_strafe_left(&mut self) {
+		self.player.strafe_left(&self.scene);
+	}
+
+	pub fn player_strafe_right(&mut self) {
+		self.player.strafe_right(&self.scene);
+	}
+
+	pub fn player_turn_left(&mut self) {
+		self.player.turn_left();
+	}
+
+	pub fn player_turn_right(&mut self) {
+		self.player.turn_right();
+	}
+
 	pub fn load_level(json_str: &str) -> FourteenScrewsDemo {
 		let json: serde_json::Value = serde_json::from_str(json_str).ok().unwrap();
 		
 		let scene    = Scene::from_json(&json["scene"]).ok().unwrap();
-		let player   = Camera::from_json(&json["camera"]).ok().unwrap();
+		let camera   = Camera::from_json(&json["camera"]).ok().unwrap();
 		let renderer = Renderer::from_json(&json["renderer"]).ok().unwrap();
 
+		let player = player::Player::new(camera, PLAYER_MOVE_SPEED, PLAYER_TURN_SPEED, PLAYER_MARGIN);
+
 		FourteenScrewsDemo { scene, player, renderer }
 	}
 
 	pub fn render(&mut self, buf: &mut[u8]) {
-		self.renderer.render(buf, &self.scene, &self.player);
-		self.player.rotate(1);
+		self.renderer.render(buf, &self.scene, &self.player.camera);
 	}
 }

+ 288 - 0
demo/src/player.rs

@@ -0,0 +1,288 @@
+use fourteen_screws::{ Camera, Scene };
+use fourteen_screws::maths;
+use fourteen_screws::maths::{ ToFixedPoint, FromFixedPoint };
+use fourteen_screws::trig;
+use fourteen_screws::Tile;
+
+use wasm_bindgen::prelude::*;
+use web_sys;
+
+macro_rules! log {
+	( $( $t:tt )* ) => {
+		web_sys::console::log_1(&format!( $( $t )* ).into());
+	}
+}
+
+#[derive(PartialEq)]
+pub enum HitResult {
+	Nothing,
+	SlideX,
+	SlideY,
+	WallX,
+	WallY,
+}
+
+pub struct Player {
+	pub camera: Camera,
+	move_speed: i32,
+	rotate_speed: i32,
+	margin: i32,
+}
+
+impl Player {
+	pub fn new(camera: Camera, move_speed: i32, rotate_speed: i32, margin: i32) -> Player {
+		Player { camera, move_speed, rotate_speed, margin }
+	}
+
+	fn translate(&mut self, mut direction: i32, amount: i32, scene: &Scene) -> HitResult {
+		while direction >= trig::ANGLE_360 { direction -= trig::ANGLE_360; }
+		while direction < trig::ANGLE_0    { direction += trig::ANGLE_360; }
+
+		let xp = self.camera.x();
+		let yp = self.camera.y();
+
+		let half_tile = fourteen_screws::TILE_SIZE >> 1;
+
+		// get bounds of the tile player currently occupies
+		let x_left   = xp & 0xFFC0;
+		let y_top    = yp & 0xFFC0;
+		let x_right  = x_left + fourteen_screws::TILE_SIZE;
+		let y_bottom = y_top + fourteen_screws::TILE_SIZE;
+
+		let mut hit_result = HitResult::Nothing;
+
+		let mut x1 = xp + maths::mul(trig::cos(direction), amount.to_fp()).to_i32();
+		let mut y1 = yp + maths::mul(trig::sin(direction), amount.to_fp()).to_i32();
+		
+		let grid_x = x_left / fourteen_screws::TILE_SIZE;
+		let grid_y = y_top / fourteen_screws::TILE_SIZE;
+
+		if x1 < xp { // are we moving left
+			if let Tile::Wall(wall) = scene.x_wall(grid_x, grid_y) {
+				if !wall.passable && (x1 < x_left || (x1 - x_left).abs() < self.margin) { // we crossed the wall or we're too close
+					log!("Blocked Left");
+					x1 = xp;
+					hit_result = HitResult::SlideX;
+				}
+			}
+		}
+
+		if x1 > xp { // are we moving right
+			if let Tile::Wall(wall) = scene.x_wall(grid_x + 1, grid_y) { // wall found in current square (right edge)
+				if !wall.passable && (x1 > x_right || (x_right - x1).abs() < self.margin) { // we crossed the wall or we're too close
+					x1 = xp;
+					hit_result = HitResult::SlideX;
+				}
+			} else if let Tile::OutOfBounds = scene.x_wall(grid_x + 1, grid_y) {
+				log!("TILE IS OUT OF BOUNDS");
+			}
+		}
+
+		if y1 < yp { // are we moving up			
+			if let Tile::Wall(wall) = scene.y_wall(grid_x, grid_y) {
+				if !wall.passable && (y1 < y_top || (y1 - y_top).abs() < self.margin) {
+					log!("Blocked Up");
+					y1 = yp;
+					hit_result = HitResult::SlideY;
+				}
+			}
+		}
+
+		if y1 > yp { // are we moving down
+			if let Tile::Wall(wall) = scene.y_wall(grid_x, grid_y + 1) {
+				if !wall.passable && (y1 > y_bottom || (y_bottom - y1).abs() < self.margin) {
+					log!("Blocked Down");
+					y1 = yp;
+					hit_result = HitResult::SlideY;
+				}
+			}
+		}
+
+		// A wall or object hasn't been hit yet. We must look further.
+		// The current grid square will be divided into four regions:
+		// A = top left; B = top right; C = bottom left; D = bottom right
+		// Each of these regions will be checked to see if the player's new position
+		// (x1, y1) is close to a wall or object that borders one of these regions.
+		// Each grid square is 64x64 units, so each region to check is 32x32 units
+
+
+		if hit_result == HitResult::Nothing {
+			if y1 < (y_top + half_tile) {    // new y position falls in top half
+				
+				// check region A-top left area of grid
+				if x1 < x_left + half_tile { // new x position falls in left half
+
+					// check adjacent x wall (to left)
+					if let Tile::Wall(wall) = scene.x_wall(grid_x, grid_y - 1) { 
+						if !wall.passable && y1 < (y_top + self.margin) { // adjacent x wall found and new y coord is within 28 units
+							if x1 < x_left + self.margin {
+								if xp > x_left + (self.margin - 1) {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								} else {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								}
+							}
+						}
+					}
+
+					// check adjacent y wall (above)
+					if let Tile::Wall(wall) = scene.y_wall(grid_x - 1, grid_y) {
+						if !wall.passable && x1 < x_left + self.margin {
+							if y1 < y_top + self.margin {
+								if yp > y_top + (self.margin - 1) {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								} else {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								}
+							}
+						}
+					}
+				}
+
+				// check region B-top right area
+				if x1 > x_right - half_tile && hit_result == HitResult::Nothing {
+					
+					// check adjacent x wall (to right)
+					if let Tile::Wall(wall) = scene.x_wall(grid_x + 1, grid_y - 1) {
+						if !wall.passable && y1 < y_top + self.margin {
+							if x1 > x_right - self.margin {
+								if xp < x_right - (self.margin - 1) {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								} else {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								}
+							}
+						}
+					}
+
+					// check adjacent y wall (above)
+					if let Tile::Wall(wall) = scene.y_wall(grid_x + 1, grid_y) {
+						if !wall.passable && x1 > x_right - self.margin {
+							if y1 < y_top + self.margin {
+								if yp < y_top + (self.margin - 1) {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								} else {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								}
+							}
+						}
+					}
+				}
+			}
+
+			// check region C-bottom left area
+			if y1 > y_top + half_tile && hit_result == HitResult::Nothing {
+				if x1 < x_left + half_tile {					
+					
+					// check adjacent x wall (to left)
+					if let Tile::Wall(wall) = scene.x_wall(grid_x, grid_y + 1) {
+						if !wall.passable && y1 > y_bottom - self.margin {
+							if x1 < x_left + self.margin {
+								if xp > x_left + (self.margin - 1) {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								} else {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								}
+							}
+						}
+					}
+					
+					// check adjacent y wall (below)
+					if let Tile::Wall(wall) = scene.y_wall(grid_x - 1, grid_y + 1) {
+						if !wall.passable && x1 < x_left + self.margin {
+							if y1 > y_bottom - self.margin {
+								if yp < y_bottom - (self.margin - 1) {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								} else {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								}
+							}
+						}
+					}
+				}
+
+				// check region D-bottom right area
+				if x1 > x_right - half_tile && hit_result == HitResult::Nothing {
+					
+					// check adjacent x wall (to right)
+					if let Tile::Wall(wall) = scene.x_wall(grid_x + 1, grid_y + 1) {
+						if !wall.passable && y1 > y_bottom - self.margin {
+							if x1 > x_right - self.margin {
+								if xp < x_right - (self.margin - 1) {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								} else {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								}
+							}
+						}
+					}
+
+					// check adjacent y wall (below)
+					if let Tile::Wall(wall) = scene.y_wall(grid_x + 1, grid_y + 1) {
+						if !wall.passable && x1 > x_right - self.margin {
+							if y1 > y_bottom - self.margin {
+								if yp < y_bottom - (self.margin - 1) {
+									y1 = yp;
+									hit_result = HitResult::SlideY;
+								} else {
+									x1 = xp;
+									hit_result = HitResult::SlideX;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+		
+		if hit_result == HitResult::SlideX && y1 == yp {
+			hit_result = HitResult::WallX;
+		}
+
+		if hit_result == HitResult::SlideY && x1 == xp {
+			hit_result = HitResult::WallY;
+		}
+
+		self.camera.move_to(x1, y1);
+
+		hit_result
+	}
+
+	pub fn forward(&mut self, scene: &Scene) -> HitResult {
+		return self.translate(self.camera.angle(), self.move_speed, scene);
+	}
+
+	pub fn back(&mut self, scene: &Scene) -> HitResult {
+		return self.translate(self.camera.angle() + trig::ANGLE_180, self.move_speed, scene);
+	}
+
+	pub fn strafe_left(&mut self, scene: &Scene) -> HitResult {
+		return self.translate(self.camera.angle() - trig::ANGLE_90, self.move_speed, scene);
+	}
+
+	pub fn strafe_right(&mut self, scene: &Scene) -> HitResult {
+		return self.translate(self.camera.angle() + trig::ANGLE_90, self.move_speed, scene);
+	}	
+
+	pub fn turn_left(&mut self) {
+		self.camera.rotate(-self.rotate_speed);
+	}
+
+	pub fn turn_right(&mut self) {
+		self.camera.rotate(self.rotate_speed);
+	}
+}

+ 1 - 1
demo/webapp/demo-level.js

@@ -1,5 +1,5 @@
 module.exports = {
-  camera: { x: 128, y: 128, angle: 0, horizon: 100 },
+  camera: { x: 32, y: 32, angle: 0, horizon: 100 },
   scene: {
   	width: 5,
   	height: 5,

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 2
demo/webapp/index.js


+ 1 - 0
engine/Cargo.toml

@@ -25,3 +25,4 @@ wasm-bindgen = "0.2.86"
 shared = { path = "../shared" }
 macros = { path = "../macros" }
 num-traits = "0.2.15"
+itertools = "0.11.0"

+ 0 - 237
engine/src/collision.rs

@@ -1,237 +0,0 @@
-use crate::maths::trig;
-
-#[derive(PartialEq)]
-enum HitResult {
-	Nothing,
-	SlideX,
-	SlideY,
-	WallX,
-	WallY,
-}
-
-struct BoundingBox {
-	margin: i32
-}
-
-impl BoundingBox {
-	fn move_player(&mut self, mut direction: i32, amount: i32) -> HitResult {
-		while direction >= trig::ANGLE_360 { direction -= trig::ANGLE_360; }
-		while direction < trig::ANGLE_0    { direction += trig::ANGLE_360; }
-
-		let xp = self.player.x;
-		let yp = self.player.y;
-
-		// get bounds of the tile player currently occupies
-		let x_left   = xp & 0xFFC0;
-		let y_top    = yp & 0xFFC0;
-		let x_right  = x_left + consts::TILE_SIZE;
-		let y_bottom = y_top + consts::TILE_SIZE;
-
-		let mut hit_result = HitResult::Nothing;
-
-		let mut x1 = xp + fp::mul(trig::cos(direction), amount.to_fp()).to_i32();
-		let mut y1 = yp + fp::mul(trig::sin(direction), amount.to_fp()).to_i32();
-		
-		let grid_x = x_left / consts::TILE_SIZE;
-		let grid_y = y_top / consts::TILE_SIZE;
-
-		if x1 < xp { // are we moving left
-			if let raycast::Tile::Wall(_, passable) = self.world.x_wall(grid_x, grid_y) {
-				if !passable && (x1 < x_left || (x1 - x_left).abs() < 28) { // we crossed the wall or we're too close
-					x1 = xp;
-					hit_result = HitResult::SlideX;
-				}
-			}
-		}
-
-		if x1 > xp { // are we moving right
-			if let raycast::Tile::Wall(_, passable) = self.world.x_wall(grid_x + 1, grid_y) { // wall found in current square (right edge)
-				if !passable && (x1 > x_right || (x_right - x1).abs() < 28) { // we crossed the wall or we're too close
-					x1 = xp;
-					hit_result = HitResult::SlideX;
-				}
-			}
-		}
-
-		if y1 < yp { // are we moving up			
-			if let raycast::Tile::Wall(_, passable) = self.world.y_wall(grid_x, grid_y) {
-				if !passable && (y1 < y_top || (y1 - y_top).abs() < 28) {
-					y1 = yp;
-					hit_result = HitResult::SlideY;
-				}
-			}
-		}
-
-		if y1 > yp { // are we moving down
-			if let raycast::Tile::Wall(_, passable) = self.world.y_wall(grid_x, grid_y + 1) {
-				if !passable && (y1 > y_bottom || (y_bottom - y1).abs() < 28) {
-					y1 = yp;
-					hit_result = HitResult::SlideY;
-				}
-			}
-		}
-
-		// A wall or object hasn't been hit yet. We must look further.
-		// The current grid square will be divided into four regions:
-		// A = top left; B = top right; C = bottom left; D = bottom right
-		// Each of these regions will be checked to see if the player's new position
-		// (x1, y1) is close to a wall or object that borders one of these regions.
-		// Each grid square is 64x64 units, so each region to check is 32x32 units
-
-
-		if hit_result == HitResult::Nothing {
-			if y1 < (y_top + 32) {    // new y position falls in top half
-				
-				// check region A-top left area of grid
-				if x1 < x_left + 32 { // new x position falls in left half
-
-					// check adjacent x wall (to left)
-					if let raycast::Tile::Wall(_, x_passable) = self.world.x_wall(grid_x, grid_y - 1) { 
-						if !x_passable && y1 < (y_top + 28) { // adjacent x wall found and new y coord is within 28 units
-							if x1 < x_left + 28 {
-								if xp > x_left + 27 {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								} else {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								}
-							}
-						}
-					}
-
-					// check adjacent y wall (above)
-					if let raycast::Tile::Wall(_, y_passable) = self.world.y_wall(grid_x - 1, grid_y) {
-						if !y_passable && x1 < x_left + 28 {
-							if y1 < y_top + 28 {
-								if yp > y_top + 27 {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								} else {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								}
-							}
-						}
-					}
-				}
-
-				// check region B-top right area
-				if x1 > x_right - 32 && hit_result == HitResult::Nothing {
-					
-					// check adjacent x wall (to right)
-					if let raycast::Tile::Wall(_, x_passable) = self.world.x_wall(grid_x + 1, grid_y - 1) {
-						if !x_passable && y1 < y_top + 28 {
-							if x1 > x_right - 28 {
-								if xp < x_right - 27 {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								} else {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								}
-							}
-						}
-					}
-
-					// check adjacent y wall (above)
-					if let raycast::Tile::Wall(_, y_passable) = self.world.y_wall(grid_x + 1, grid_y) {
-						if !y_passable && x1 > x_right - 28 {
-							if y1 < y_top + 28 {
-								if yp < y_top + 27 {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								} else {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								}
-							}
-						}
-					}
-				}
-			}
-
-			// check region C-bottom left area
-			if y1 > y_top + 32 && hit_result == HitResult::Nothing {
-				if x1 < x_left + 32 {					
-					
-					// check adjacent x wall (to left)
-					if let raycast::Tile::Wall(_, x_passable) = self.world.x_wall(grid_x, grid_y + 1) {
-						if !x_passable && y1 > y_bottom - 28 {
-							if x1 < x_left + 28 {
-								if xp > x_left + 27 {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								} else {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								}
-							}
-						}
-					}
-					
-					// check adjacent y wall (below)
-					if let raycast::Tile::Wall(_, y_passable) = self.world.y_wall(grid_x - 1, grid_y + 1) {
-						if !y_passable && x1 < x_left + 28 {
-							if y1 > y_bottom - 28 {
-								if yp < y_bottom - 27 {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								} else {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								}
-							}
-						}
-					}
-				}
-
-				// check region D-bottom right area
-				if x1 > x_right - 32 && hit_result == HitResult::Nothing {
-					
-					// check adjacent x wall (to right)
-					if let raycast::Tile::Wall(_, x_passable) = self.world.x_wall(grid_x + 1, grid_y + 1) {
-						if !x_passable && y1 > y_bottom - 28 {
-							if x1 > x_right - 28 {
-								if xp < x_right - 27 {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								} else {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								}
-							}
-						}
-					}
-
-					// check adjacent y wall (below)
-					if let raycast::Tile::Wall(_, y_passable) = self.world.y_wall(grid_x + 1, grid_y + 1) {
-						if !y_passable && x1 > x_right - 28 {
-							if y1 > y_bottom - 28 {
-								if yp < y_bottom - 27 {
-									y1 = yp;
-									hit_result = HitResult::SlideY;
-								} else {
-									x1 = xp;
-									hit_result = HitResult::SlideX;
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-		
-		if hit_result == HitResult::SlideX && y1 == yp {
-			hit_result = HitResult::WallX;
-		}
-
-		if hit_result == HitResult::SlideY && x1 == xp {
-			hit_result = HitResult::WallY;
-		}
-
-		self.player.pos(x1, y1);
-
-		hit_result
-	}
-}

+ 2 - 0
engine/src/lib.rs

@@ -3,7 +3,9 @@ extern crate web_sys;
 pub mod trig;
 pub mod scene;
 pub mod render;
+pub mod maths;
 
 pub use crate::trig::*;
 pub use crate::scene::*;
 pub use crate::render::*;
+pub use shared::consts::*;

+ 1 - 0
engine/src/maths.rs

@@ -0,0 +1 @@
+pub use shared::fp::*;

+ 14 - 0
engine/src/render/camera.rs

@@ -66,3 +66,17 @@ impl Camera {
 		Ok(Camera::new(x, y, a, h))
 	}
 }
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn test_initialize() {
+		let camera = Camera::new(10, 15, 20, 25);
+		assert_eq!(camera.x(), 10);
+		assert_eq!(camera.y(), 15);
+		assert_eq!(camera.angle(), 20);
+		assert_eq!(camera.horizon(), 25);
+	}
+}

+ 115 - 89
engine/src/render/raycast.rs

@@ -1,5 +1,6 @@
 use crate::scene::{ Tile, Scene };
 use crate::trig;
+use itertools::Itertools;
 use shared::consts;
 use shared::fp;
 use shared::fp::{ ToFixedPoint, FromFixedPoint };
@@ -20,22 +21,29 @@ impl Intersection {
 	}
 }
 
-pub struct RayCaster {}
-
-impl RayCaster {
-	pub fn new() -> RayCaster {
-		RayCaster {}
-	}
+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,
+	direction: i32,
+	scene: &'a Scene,
+	origin_x: i32,
+	origin_y: i32,
+
+	cast_ray: fn(&mut Self) -> Option<Intersection>,
+	check_undefined: fn(&Self) -> bool
+}
 
-	fn find_horizontal_intersect(&self, origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Vec<Intersection> {
-		let step_x: i32; // distance to next vertical intersect
-		let step_y: i32; // distance to next horizontal intersect
-		let mut x: i32;  // x coordinate of current ray intersect
-		let mut y: i32;  // y coordinate of current ray intersect
+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;
 
-		let mut intersects = Vec::new();
-
 		// 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);
@@ -53,46 +61,16 @@ impl RayCaster {
 			flipped = false;
 		}
 
-		if direction == trig::ANGLE_0 || direction == trig::ANGLE_180 {
-			return intersects;
-		}
-
-		// Cast x axis intersect rays, build up horizontal intersections
-		loop {
-			let grid_x = fp::div(x, consts::FP_TILE_SIZE).to_i32();
-			let grid_y = fp::div(y, consts::FP_TILE_SIZE).to_i32();
-			
-			match scene.x_wall(grid_x, grid_y) {
-				Tile::Wall(wall) => {
-					let world_x  = x.to_i32();
-					let world_y  = y.to_i32();
-					let distance = fp::mul(fp::sub(y, origin_y), trig::isin(direction)).abs();
-					let texture  = wall.texture;
-					let texture_column = world_x & (consts::TILE_SIZE - 1);
-					let intersection = Intersection::new(world_x, world_y, distance, texture, texture_column, flipped);
-					intersects.push(intersection);
-				},
-				Tile::OutOfBounds => break,
-				Tile::Empty => {}
-			}
-
-			x = fp::add(x, step_x);
-			y = fp::add(y, step_y);
-		}
-
-
-		intersects
+		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 }
 	}
 
-	fn find_vertical_intersect(&self, origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Vec<Intersection> {
+	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 mut x: i32;  // x coordinate of current ray intersect
-		let mut y: i32;  // y coordinate of current ray intersect
+		let x: i32;      // x coordinate of current ray intersect
+		let y: i32;      // y coordinate of current ray intersect
 		let flipped: bool;
 
-		let mut intersects = Vec::new();
-
 		// 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;
@@ -112,65 +90,113 @@ impl RayCaster {
 			flipped = true;
 		};
 
-		if direction == trig::ANGLE_90 || direction == trig::ANGLE_270 {
-			return intersects;
+		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.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.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);
 		}
 
-		loop {
-			let grid_x = fp::div(x, consts::FP_TILE_SIZE).to_i32();
-			let grid_y = fp::div(y, consts::FP_TILE_SIZE).to_i32();
+		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 scene.y_wall(grid_x, grid_y) {
+			match self.scene.x_wall(grid_x, grid_y) {
 				Tile::Wall(wall) => {
-					let world_x  = x.to_i32();
-					let world_y  = y.to_i32();
-					let distance = fp::mul(fp::sub(x, origin_x), trig::icos(direction)).abs();
+					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);
-					let intersection = Intersection::new(world_x, world_y, distance, texture, texture_column, flipped);
-					intersects.push(intersection);
+					result = Some(Intersection::new(world_x, world_y, distance, texture, texture_column, self.flipped));
 				},
 				Tile::OutOfBounds => break,
 				Tile::Empty => {}
 			}
 
-			x = fp::add(x, step_x);
-			y = fp::add(y, step_y);
+			self.x = fp::add(self.x, self.step_x);
+			self.y = fp::add(self.y, self.step_y);
 		}
 
-		intersects
+		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 hintersects = self.find_horizontal_intersect(origin_x, origin_y, direction, scene);
-		let vintersects = self.find_vertical_intersect(origin_x, origin_y, direction, scene);
-
-		let mut intersects = Vec::new();
-		intersects.reserve(hintersects.len() + vintersects.len());
-
-		let mut i = 0;
-		let mut j = 0;
-
-		while i < hintersects.len() && j < vintersects.len() {
-			if hintersects[i].dist < vintersects[j].dist {
-				intersects.push(hintersects[i]);
-				i += 1;
-			} else {
-				intersects.push(vintersects[j]);
-				j += 1;
-			}
-		}
+		let ray_h = Ray::horizontal(origin_x, origin_y, direction, scene);
+		let ray_v = Ray::vertical(origin_x, origin_y, direction, scene);
 
-		while i < hintersects.len() {			
-			intersects.push(hintersects[i]);
-			i += 1;
-		}
+		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()
+	}
+}
 
-		while j < vintersects.len() {
-			intersects.push(vintersects[j]);
-			j += 1;
-		}
 
-		intersects
+#[cfg(test)]
+mod test {
+	use super::*;
+
+	#[test]
+	fn name() {
+		let raycaster = RayCaster::new();
 	}
-}
+}

+ 17 - 5
engine/src/render/renderer.rs

@@ -7,6 +7,19 @@ use shared::consts;
 use shared::fp;
 use shared::fp::{ ToFixedPoint, FromFixedPoint };
 
+macro_rules! colour_to_buf {
+	($colour:expr, $buf:expr, $idx:expr) => {
+		($buf[$idx + 0], $buf[$idx + 1], $buf[$idx + 2], $buf[$idx + 3]) = $colour.tuple();
+	}
+}
+
+macro_rules! blend_colour_to_buf {
+	($colour:expr, $buf:expr, $idx:expr) => {
+		let blended = $colour.blend(&Colour::new($buf[$idx + 0], $buf[$idx + 1], $buf[$idx + 2], $buf[$idx + 3]));
+		colour_to_buf!(blended, $buf, $idx);
+	}
+}
+
 pub struct Colour {
 	pub r: u8,
 	pub g: u8,
@@ -115,7 +128,7 @@ impl Renderer {
 		let y_max = parameters[0].y_max;
 
 		for y in y_min..=y_max {
-			let mut pixel = Colour::new(0, 0, 0, 0);
+			let mut pixel = Colour::new(255, 0, 0, 255);
 			
 			let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
 			
@@ -125,15 +138,14 @@ impl Renderer {
 				let tex_y = intersect.tex_idx[y as usize];
 				pixel = pixel.blend(&intersect.texture[tex_y]);
 			}
-
-			pixel = pixel.blend(&Colour::new(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]));
-			(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]) = pixel.tuple();
+			
+			blend_colour_to_buf!(pixel, buf, idx);
 		}
 	}
 
 	fn draw_background(&self, buf: &mut[u8]) {
 
-		for y in 0..consts::PROJECTION_PLANE_HORIZON / 2 {
+		for y in 0..consts::PROJECTION_PLANE_HORIZON {
 			for x in 0..consts::PROJECTION_PLANE_WIDTH {
 				let idx: usize = 4 * (x + y * consts::PROJECTION_PLANE_WIDTH) as usize;
 				buf[idx + 0] = 0x38;

+ 23 - 4
engine/src/scene.rs

@@ -2,6 +2,7 @@ use serde_json;
 
 pub struct TextureTile {
 	pub texture: u32,
+	pub passable: bool,
 }
 
 pub enum Tile {
@@ -32,6 +33,24 @@ impl Scene {
 		x >= 0 && x < self.width && y >= 0 && y < self.height
 	}
 
+	pub fn x_obstructed(&self, x: i32, y: i32) -> bool {
+		let tile = self.x_wall(x, y);
+		match tile {
+			Tile::Wall(wall) => !wall.passable,
+			Tile::OutOfBounds => true,
+			_ => false
+		}
+	}
+
+	pub fn y_obstructed(&self, x: i32, y: i32) -> bool {
+		let tile = self.y_wall(x, y);
+		match tile {
+			Tile::Wall(wall) => !wall.passable,
+			Tile::OutOfBounds => true,
+			_ => false
+		}
+	}
+
 	pub fn y_wall(&self, x: i32, y: i32) -> &Tile {
 		if !self.is_within_bounds(x, y) { return &Tile::OutOfBounds; }
 		&self.y_walls[(x + y  * self.width) as usize]
@@ -58,22 +77,22 @@ impl Scene {
 		
 		let x_walls = json["x_walls"].as_array().unwrap().iter()
 			.map(|value|   { value.as_i64().unwrap() as u32 })
-			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1 }) } else { Tile::Empty } })
+			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1, passable: false }) } else { Tile::Empty } })
 			.collect();
 
 		let y_walls = json["y_walls"].as_array().unwrap().iter()
 			.map(|value|   { value.as_i64().unwrap() as u32 })
-			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1 }) } else { Tile::Empty } })
+			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1, passable: false }) } else { Tile::Empty } })
 			.collect();
 
 		let floor = json["floor"].as_array().unwrap().iter()
 			.map(|value|   { value.as_i64().unwrap() as u32 })
-			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1 }) } else { Tile::Empty } })
+			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1, passable: false }) } else { Tile::Empty } })
 			.collect();
 
 		let ceiling = json["ceiling"].as_array().unwrap().iter()
 			.map(|value|   { value.as_i64().unwrap() as u32 })
-			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1 }) } else { Tile::Empty } })
+			.map(|texture| { if texture > 0 { Tile::Wall(TextureTile { texture: texture - 1, passable: false }) } else { Tile::Empty } })
 			.collect();
 
 		Scene::new(width, height, x_walls, y_walls, floor, ceiling)

+ 4 - 8
macros/src/lib.rs

@@ -63,10 +63,8 @@ fn declare_step_tables() -> TokenStream {
                 if step > 0.0 {
                   step = -step;
                 }
-            } else {
-                if step < 0.0 {
-                  step = -step;
-                }
+            } else if step < 0.0 {
+                step = -step;
             }
         }
 
@@ -80,10 +78,8 @@ fn declare_step_tables() -> TokenStream {
             if step < 0.0 {
               step = -step;
             }
-        } else {
-            if step > 0.0 {
-              step = -step;
-            }
+        } else if step > 0.0 {
+            step = -step;
         }
 
         y_step[i] = step.to_fp();

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff