Преглед на файлове

Beginning project refactor

Gary Munnelly преди 2 години
родител
ревизия
acff46e02b
променени са 6 файла, в които са добавени 487 реда и са изтрити 3 реда
  1. 3 3
      engine/src/consts.rs
  2. 2 0
      engine/src/lib.rs
  3. 2 0
      engine/src/maths.rs
  4. 113 0
      engine/src/maths/fp.rs
  5. 59 0
      engine/src/maths/trig.rs
  6. 308 0
      engine/src/render/mod.rs

+ 3 - 3
engine/src/consts.rs

@@ -1,10 +1,10 @@
 use crate::fp::ToFixedPoint;
 
-pub const PROJECTION_PLANE_HEIGHT: i32 = 200;
-pub const PROJECTION_PLANE_WIDTH: i32 = 320;
+pub const PROJECTION_PLANE_HEIGHT: i32  = 200;
+pub const PROJECTION_PLANE_WIDTH: i32   = 320;
 pub const PROJECTION_PLANE_HORIZON: i32 = PROJECTION_PLANE_HEIGHT / 2;
 
-pub const TILE_SIZE: i32 = 64;
+pub const TILE_SIZE: i32    = 64;
 pub const FP_TILE_SIZE: i32 = TILE_SIZE << 16;
 
 pub const TEXTURE_WIDTH: usize = 64;

+ 2 - 0
engine/src/lib.rs

@@ -8,6 +8,8 @@ mod trig;
 mod utils;
 mod raycast;
 mod fp;
+mod render;
+mod maths;
 
 macro_rules! log {
 	( $( $t:tt )* ) => {

+ 2 - 0
engine/src/maths.rs

@@ -0,0 +1,2 @@
+pub mod fp;
+pub mod trig;

+ 113 - 0
engine/src/maths/fp.rs

@@ -0,0 +1,113 @@
+const FP_SHIFT: i32 = 16;
+const FP_MULT: f64  = 65536.0;
+const FP_HALF: f64  = 32768.0;
+
+const FP_FLOOR_MASK: i32 = !((1 << FP_SHIFT) - 1);
+
+pub trait ToFixedPoint {
+    fn to_fp(&self) -> i32;
+}
+
+pub trait FromFixedPoint {
+    fn to_f64(&self) -> f64;
+    fn to_i32(&self) -> i32;
+}
+
+impl ToFixedPoint for f64 {
+    fn to_fp(&self) -> i32 {
+        (*self * FP_MULT) as i32
+    }
+}
+
+impl ToFixedPoint for i32 {
+    fn to_fp(&self) -> i32 {
+        *self << FP_SHIFT
+    }
+}
+
+impl FromFixedPoint for i32 {
+	fn to_f64(&self) -> f64 {
+		*self as f64 / FP_MULT
+	}
+
+    fn to_i32(&self) -> i32 {
+    	*self >> FP_SHIFT
+    }
+}
+
+pub const fn add(a: i32, b: i32) -> i32 {
+	a + b
+}
+
+pub const fn sub(a: i32, b: i32) -> i32 {
+	a - b
+}
+
+pub const fn mul(a: i32, b: i32) -> i32 {
+	((a as i64 * b as i64) >> FP_SHIFT) as i32
+}
+
+pub const fn div(a: i32, b: i32) -> i32 {
+	(((a as i64)  << FP_SHIFT) / b as i64) as i32
+}
+
+pub const fn floor(a: i32) -> i32 {
+	a & FP_FLOOR_MASK
+}
+
+#[cfg(test)]
+mod test {
+	use super::*;
+
+	#[test]
+	fn f64_add() {
+		let test_pairs = [
+			(0.5, 0.5),
+			(-0.754, 0.123)
+		];
+
+		for (a, b) in test_pairs {
+			let fp_sum = add(a.to_fp(), b.to_fp());
+			float_cmp::assert_approx_eq!(f64, fp_sum.to_f64(), a + b, epsilon = 0.003, ulps = 2)
+		}
+	}
+
+	#[test]
+	fn f64_sub() {
+		let test_pairs = [
+			(0.5, 0.5),
+			(-0.754, 0.123)
+		];
+
+		for (a, b) in test_pairs {
+			let fp_diff = sub(a.to_fp(), b.to_fp());
+			float_cmp::assert_approx_eq!(f64, fp_diff.to_f64(), a - b, epsilon = 0.003, ulps = 2)
+		}
+	}
+
+	#[test]
+	fn f64_mul() {
+		let test_pairs = [
+			(0.5, 0.5),
+			(-0.754, 0.123)
+		];
+
+		for (a, b) in test_pairs {
+			let fp_prod = mul(a.to_fp(), b.to_fp());
+			float_cmp::assert_approx_eq!(f64, fp_prod.to_f64(), a * b, epsilon = 0.003, ulps = 2)
+		}
+	}
+
+	#[test]
+	fn f64_div() {
+		let test_pairs = [
+			(0.5, 0.5),
+			(-0.754, 0.123)
+		];
+
+		for (a, b) in test_pairs {
+			let fp_quot = div(a.to_fp(), b.to_fp());
+			float_cmp::assert_approx_eq!(f64, fp_quot.to_f64(), a / b, epsilon = 0.003, ulps = 2)
+		}
+	}
+}

+ 59 - 0
engine/src/maths/trig.rs

@@ -0,0 +1,59 @@
+use crate::consts::{ PROJECTION_PLANE_WIDTH, MAX_RAY_LENGTH };
+use core::f64::consts::PI;
+
+include!(concat!(env!("OUT_DIR"), "/lookup.rs"));
+
+pub const ANGLE_60:  i32 = PROJECTION_PLANE_WIDTH;
+
+pub const ANGLE_0:   i32 = 0;
+pub const ANGLE_5:   i32 = ANGLE_60 / 12;
+pub const ANGLE_10:  i32 = ANGLE_60 / 6;
+pub const ANGLE_30:  i32 = ANGLE_60 / 2;
+pub const ANGLE_90:  i32 = ANGLE_30 * 3;
+pub const ANGLE_180: i32 = ANGLE_60 * 3;
+pub const ANGLE_270: i32 = ANGLE_90 * 3;
+pub const ANGLE_360: i32 = ANGLE_60 * 6;
+
+pub fn radian(angle: i32) -> f64 {
+	angle as f64 * PI / ANGLE_180 as f64
+}
+
+pub fn cos(degrees: i32) -> i32 {
+	COS[degrees as usize]
+}
+
+pub fn sin(degrees: i32) -> i32 {
+	SIN[degrees as usize]
+}
+
+pub fn tan(degrees: i32) -> i32 {
+	TAN[degrees as usize]
+}
+
+pub fn icos(degrees: i32) -> i32 {
+	ICOS[degrees as usize]
+}
+
+pub fn isin(degrees: i32) -> i32 {
+	ISIN[degrees as usize]
+}
+
+pub fn itan(degrees: i32) -> i32 {
+	ITAN[degrees as usize]
+}
+
+pub fn xstep(degrees: i32) -> i32 {
+	X_STEP[degrees as usize]
+}
+
+pub fn ystep(degrees: i32) -> i32 {
+	Y_STEP[degrees as usize]
+}
+
+pub fn fisheye_correction(degrees: i32) -> i32 {
+	FISHEYE[degrees as usize]
+}
+
+pub fn wall_height(dist: i32) -> i32 {
+	WALL_HEIGHT[dist.min(MAX_RAY_LENGTH) as usize]
+}

+ 308 - 0
engine/src/render/mod.rs

@@ -0,0 +1,308 @@
+use crate::maths::trig;
+use crate::maths::fp;
+use crate::maths::fp::{ ToFixedPoint, FromFixedPoint };
+use crate::consts;
+
+#[derive(Debug, Copy, Clone)]
+enum TextureCode {
+	None,
+	Wall(u8, i32, bool),
+	Floor(u8, i32, i32),
+	Ceiling(u8, i32, i32),
+}
+
+#[derive(Debug, Copy, Clone)]
+struct Slice {
+	pub texture: TextureCode,
+	pub distance: i32,
+}
+
+impl Slice {
+	fn new(texture: TextureCode, distance: i32) -> Slice {
+		Slice { texture, distance }
+	}
+}
+
+enum Tile {
+	OutOfBounds,
+	Wall(u8, i32),
+	Floor,
+	Ceiling,
+}
+
+struct Camera {
+	x: i32,
+	y: i32,
+	angle: i32,
+	horizon: i32,
+}
+
+impl Camera {
+	pub fn new(x: i32, y: i32, angle: i32, horizon: i32) -> Camera {
+		Camera { x: x.to_fp(), y: y.to_fp() , angle, horizon }
+	}
+
+	pub fn default() -> Camera {
+		Camera::new(0, 0, 0, consts::PROJECTION_PLANE_HORIZON)
+	}
+
+	pub fn x(&self) -> i32 {
+		self.x.to_i32()
+	}
+
+	pub fn set_x(&mut self, x: i32) {
+		self.x = x.to_fp();
+	}
+
+	pub fn y(&self) -> i32 {
+		self.y.to_i32()
+	}
+
+	pub fn set_y(&mut self, y: i32) {
+		self.y = y.to_fp()
+	}
+}
+
+struct Scene {
+	width: i32,
+	height: i32,
+	y_walls: Vec<Tile>,
+	x_walls: Vec<Tile>,
+	floor: Vec<Tile>,
+	ceiling: Vec<Tile>,
+}
+
+impl Scene {
+	pub fn new(width: i32, height: i32) -> Result<Scene, &'static str> {
+		if width < 0 || height < 0 {
+			return Err("Width and height must be positive values");
+		}
+
+		let y_walls = Vec::new();
+		let x_walls = Vec::new();
+		let floor   = Vec::new();
+		let ceiling = Vec::new();
+
+		Ok(Scene { width, height, y_walls, x_walls, floor, ceiling })
+	}
+
+	pub fn is_within_bounds(&self, x: i32, y: i32) -> bool {
+		x >= 0 && x < self.width && y >= 0 && y < self.height
+	}
+
+	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]
+	}
+
+	pub fn x_wall(&self, x: i32, y: i32) -> &Tile {
+		if !self.is_within_bounds(x, y) { return &Tile::OutOfBounds; }
+		&self.x_walls[(x + y  * self.width) as usize]
+	}
+}
+
+struct RenderConfig {
+
+}
+
+impl RenderConfig {
+	pub fn new() -> RenderConfig {
+		RenderConfig {}
+	}
+
+	pub fn default() -> RenderConfig {
+		RenderConfig {}
+	}
+}
+
+struct Renderer {
+
+}
+
+impl Renderer {
+	pub fn new(config: &RenderConfig) -> Renderer {
+		Renderer {}
+	}
+
+	fn draw_to_buffer(&self, buf: &mut[u8]) {
+
+	}
+
+	pub fn render(&self, buf: &mut[u8], scene: &Scene, camera: &Camera) {
+
+		// theta is the direction player is facing
+		// need to start out sweep 30 degrees to the left
+		let mut angle = if camera.angle < trig::ANGLE_30 {
+			camera.angle - trig::ANGLE_30 + trig::ANGLE_360
+		} else {
+			camera.angle - trig::ANGLE_30
+		};
+
+		// ray casting uses fixed point notation, so convert player coordinates to fixed point
+		let origin_x = camera.x.to_fp();
+		let origin_y = camera.y.to_fp();
+
+		// sweep of the rays will be through 60 degrees
+		for sweep in 0..trig::ANGLE_60 {
+			let slices = self.find_wall_intersections(origin_x, origin_y, angle, scene);
+		// 	if slices.len() <= 0 { continue; }
+		// 	let mut parameters: Vec<ColumnRenderParameters> = Vec::new();
+		// 	parameters.reserve(slices.len());
+
+		// 	// for each slice, get a reference to its texture and figure out how
+		// 	// it should be drawn
+		// 	for slice in slices {
+		// 		let dist = fp::div(slice.distance, trig::fisheye_correction(sweep)).to_i32();
+		// 		let wall_height: i32 = trig::wall_height(dist);
+		// 		let y_min = std::cmp::max(0, self.world.horizon() - wall_height / 2);
+		// 		let y_max = std::cmp::min(consts::PROJECTION_PLANE_HEIGHT - 1, self.world.horizon() + wall_height / 2);
+		// 		let step: f64 = consts::TEXTURE_HEIGHT as f64 / wall_height as f64;
+				
+		// 		if let raycast::TextureCode::Wall(code, texture_column, flipped) = slice.texture {
+		// 			let texture = self.textures.get(code, texture_column, flipped);
+		// 			let tex_pos: f64 = (y_min as f64 - *self.world.horizon() as f64 + wall_height as f64 / 2.0) * step;
+		// 			parameters.push(ColumnRenderParameters::new(texture, step, wall_height, tex_pos, y_min, y_max))	
+		// 		}
+		// 	}
+
+		// 	self.draw_to_buffer(buf, origin_x, origin_y, angle, sweep, &mut parameters);
+
+		// 	angle += 1;
+		// 	if angle >= trig::ANGLE_360 {
+		// 		angle -= trig::ANGLE_360;
+		// 	}
+		}
+	}
+
+	fn find_horizontal_intersect(&self, origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Vec<Slice> {
+		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 flipped: bool;
+
+		let mut slices = 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::xstep(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::xstep(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;
+		}
+
+		if direction == trig::ANGLE_0 || direction == trig::ANGLE_180 {
+			return slices;
+			// return Slice::new(TextureCode::None, consts::FP_MAX_RAY_LENGTH);
+		}
+
+		// Cast x axis intersect rays, build up xSlice
+
+		while scene.is_within_bounds(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
+			if let Tile::Wall(texture, _) = scene.y_wall(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
+				let slice = Slice::new(
+					TextureCode::Wall(*texture, x.to_i32() & (consts::TILE_SIZE - 1), flipped),
+					fp::mul(fp::sub(y, origin_y), trig::isin(direction)).abs(),					
+				);
+				slices.push(slice);
+			}
+
+			x = fp::add(x, step_x);
+			y = fp::add(y, step_y);
+		}
+
+		slices
+	}
+
+	fn find_vertical_intersect(&self, origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Vec<Slice> {
+		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 flipped: bool;
+
+		let mut slices = 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;
+			step_y = trig::ystep(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::ystep(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;
+		};
+
+		if direction == trig::ANGLE_90 || direction == trig::ANGLE_270 {
+			return slices;
+		}
+
+		// Cast y axis intersect rays, build up ySlice
+		while scene.is_within_bounds(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
+			if let Tile::Wall(texture, _) = scene.x_wall(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
+				let slice = Slice::new(
+					TextureCode::Wall(*texture, y.to_i32() & (consts::TILE_SIZE - 1), flipped),
+					fp::mul(fp::sub(x, origin_x), trig::icos(direction)).abs()
+				);
+
+				slices.push(slice);
+			}
+
+			x = fp::add(x, step_x);
+			y = fp::add(y, step_y);
+		}
+
+		slices
+	}
+
+	fn find_wall_intersections(&self, origin_x: i32, origin_y: i32, direction: i32, scene: &Scene) -> Vec<Slice> {
+		let hslices = self.find_horizontal_intersect(origin_x, origin_y, direction, scene);
+		let vslices = self.find_vertical_intersect(origin_x, origin_y, direction, scene);
+		
+		let mut slices = Vec::new();
+		slices.reserve(hslices.len() + vslices.len());
+
+		let mut i = 0;
+		let mut j = 0;
+
+		while i < hslices.len() && j < vslices.len() {
+			if hslices[i].distance < vslices[j].distance {
+				slices.push(hslices[i]);
+				i += 1;
+			} else {
+				slices.push(vslices[j]);
+				j += 1;
+			}
+		}
+
+		while i < hslices.len() {			
+			slices.push(hslices[i]);
+			i += 1;
+		}
+
+		while j < vslices.len() {
+			slices.push(vslices[j]);
+			j += 1;
+		}
+
+		slices
+	}
+}