浏览代码

Refactoring raycaster into structs

Gary Munnelly 2 年之前
父节点
当前提交
6e7f473ec9
共有 3 个文件被更改,包括 287 次插入546 次删除
  1. 0 512
      engine/src/lib.rs
  2. 286 33
      engine/src/render.rs
  3. 1 1
      engine/src/scene.rs

+ 0 - 512
engine/src/lib.rs

@@ -1,6 +1,3 @@
-// use base64::{Engine as _, engine::general_purpose};
-// use fp::{ ToFixedPoint, FromFixedPoint };
-// use wasm_bindgen::prelude::*;
 extern crate web_sys;
 
 pub mod trig;
@@ -10,512 +7,3 @@ pub mod render;
 pub use crate::trig::*;
 pub use crate::scene::*;
 pub use crate::render::*;
-
-// mod consts;
-// mod trig;
-// mod utils;
-// mod raycast;
-// mod fp;
-// mod render;
-// mod maths;
-// mod collision;
-
-// macro_rules! log {
-// 	( $( $t:tt )* ) => {
-// 		web_sys::console::log_1(&format!( $( $t )* ).into());
-// 	}
-// }
-
-// struct ColumnRenderParameters<'a> {
-// 	pub texture: &'a [u8],
-// 	pub step: f64,
-// 	pub wall_height: i32,
-// 	pub tex_pos: f64,
-// 	pub y_min: i32,
-// 	pub y_max: i32,
-// }
-
-// impl<'a> ColumnRenderParameters<'_> {
-// 	pub fn new(texture: &'a[u8], step: f64, wall_height: i32, tex_pos:f64, y_min: i32, y_max: i32) -> ColumnRenderParameters {
-// 		ColumnRenderParameters { texture, step, wall_height, tex_pos, y_min, y_max }
-// 	}
-
-// 	pub fn step(&mut self) {
-// 		self.tex_pos += self.step;
-// 	}
-// }
-
-// struct TextureMap {
-// 	texture_width: usize,
-// 	texture_height: usize,
-// 	texture_size: usize,
-// 	num_textures: usize,
-// 	textures: Vec<u8>
-// }
-
-// impl TextureMap {
-// 	pub fn new(texture_width: usize, texture_height: usize, textures: Vec<u8>) -> TextureMap {
-// 		let texture_size = texture_width * texture_height;
-// 		let num_textures = textures.len() / (texture_size * 4);
-// 		TextureMap { texture_width, texture_height, texture_size, num_textures, textures }
-// 	}
-
-// 	pub fn empty() -> TextureMap {
-// 		TextureMap { texture_width: 0, texture_height: 0, texture_size: 0, num_textures: 0, textures: vec![] }
-// 	}
-
-// 	pub fn get(&self, code: u8, column: i32, flipped: bool) -> &[u8] {
-// 		let column = if flipped { self.texture_width - 1 - column as usize } else { column as usize };
-// 		let pos: usize = (self.texture_size * code as usize + column as usize * self.texture_width) * 4 as usize;
-// 		&self.textures[pos..pos + self.texture_size]
-// 	}
-// }
-
-// #[derive(PartialEq)]
-// enum HitResult {
-// 	Nothing,
-// 	SlideX,
-// 	SlideY,
-// 	WallX,
-// 	WallY,
-// }
-
-// #[wasm_bindgen]
-// pub struct Cluiche {
-// 	world: raycast::World,
-// 	player: raycast::Player,
-// 	textures: TextureMap,
-// }
-
-// fn alpha_blend(c1: f64, a1: f64, c2: f64, a2: f64, ao: f64) -> f64 {
-// 	(c1 * a1 + c2 * a2 * (1.0 - a1)) / ao
-// }
-
-// fn blend_colours(r1: u8, g1: u8, b1: u8, a1: u8, r2:u8, g2:u8, b2:u8, a2:u8) -> (u8, u8, u8, u8) {
-// 	let fa1 = a1 as f64 / 255.0;
-// 	let fa2 = a2 as f64 / 255.0;
-// 	let fao = alpha_blend(1.0, fa1, 1.0, fa2, 1.0);
-
-// 	let r = alpha_blend(r1 as f64, fa1, r2 as f64, fa2, fao) as u8;
-// 	let g = alpha_blend(g1 as f64, fa1, g2 as f64, fa2, fao) as u8;
-// 	let b = alpha_blend(b1 as f64, fa1, b2 as f64, fa2, fao) as u8;
-// 	let a = (255.0 * fao) as u8;
-
-// 	(r, g, b, a)
-// }
-
-// #[wasm_bindgen]
-// impl Cluiche {
-// 	pub fn new() -> Cluiche {
-// 		let world = raycast::World::new(13, 6, "WHHhHWHWHHHcWvOOOOVOVOOOcVVOOOOvOvOOOcVVOOOOVOXOOOsVVOOOOXOVOOOWVWHHHHWHWHHHWW").unwrap();
-// 		let player = raycast::Player::new(160, 160, 0, 8, 10);
-// 		let textures = TextureMap::empty();
-// 		Cluiche { world, player, textures }
-// 	}
-
-// 	pub fn load_textures(&mut self, encoded: &str) {
-// 		let bytes: Vec<u8> = general_purpose::STANDARD_NO_PAD.decode(encoded).expect("failed to decode textures");
-// 		self.textures = TextureMap::new(consts::TEXTURE_WIDTH, consts::TEXTURE_HEIGHT, bytes);
-// 	}
-
-// 	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
-// 	}
-
-// 	pub fn player_forward(&mut self) {
-// 		self.move_player(self.player.rotation, self.player.move_speed);
-// 	}
-
-// 	pub fn player_back(&mut self) {
-// 		self.move_player(self.player.rotation + trig::ANGLE_180, self.player.move_speed);
-// 	}
-
-// 	pub fn player_strafe_left(&mut self) {
-// 		self.move_player(self.player.rotation - trig::ANGLE_90, self.player.move_speed);
-// 	}
-
-// 	pub fn player_strafe_right(&mut self) {
-// 		self.move_player(self.player.rotation + trig::ANGLE_90, self.player.move_speed);
-// 	}	
-
-// 	pub fn player_turn_left(&mut self) {
-// 		self.player.rotation(self.player.rotation - self.player.rotate_speed);
-// 	}
-
-// 	pub fn player_turn_right(&mut self) {
-// 		self.player.rotation(self.player.rotation + self.player.rotate_speed);
-// 	}
-
-// 	pub fn player_look_up(&mut self) {
-// 		self.world.move_horizon(5);
-// 	}
-
-// 	pub fn player_look_down(&mut self) {
-// 		self.world.move_horizon(-5);
-// 	}
-
-// 	fn draw_wall_column(&self, buf: &mut[u8], origin_x: i32, origin_y: i32, direction: i32, column: i32, parameters: &mut Vec<ColumnRenderParameters>) {
-// 		let y_min = parameters[0].y_min;
-// 		let y_max = parameters[0].y_max;
-
-// 		for y in y_min..=y_max {
-// 			let mut r: u8 = 0;
-// 			let mut g: u8 = 0;
-// 			let mut b: u8 = 0;
-// 			let mut a: u8 = 0;
-			
-// 			let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
-
-// 			for slice in parameters.iter_mut() {
-// 				if y < slice.y_min || y > slice.y_max { break; }
-// 				let tex_y = (slice.tex_pos.clamp(0.0, 63.0) as usize) * 4;
-// 				slice.step();
-// 				if a >= 255 { continue; }
-// 				(r, g, b, a) = blend_colours(r, g, b, a, slice.texture[tex_y + 0], slice.texture[tex_y + 1], slice.texture[tex_y + 2], slice.texture[tex_y + 3]);
-// 			}
-
-// 			if a < 255 {
-// 				if y >= *self.world.horizon() {
-// 					let floor = self.world.find_floor_intersection(origin_x, origin_y, direction, y, column);
-
-// 					if let raycast::TextureCode::Floor(code, x, y) = floor {
-// 						let texture = self.textures.get(code, x, false);
-// 						let tex_y = (y * 4) as usize;
-// 						(r, g, b, a) = blend_colours(r, g, b, a, texture[tex_y + 0], texture[tex_y + 1], texture[tex_y + 2], texture[tex_y + 3]);
-// 					} else {
-// 						(r, g, b, a) = blend_colours(r, g, b, a, 0x70, 0x70, 0x70, 0xFF);
-// 					}
-// 				} else {
-// 					let ceiling = self.world.find_ceiling_intersection(origin_x, origin_y, direction, y, column);
-
-// 					if let raycast::TextureCode::Ceiling(code, x, y) = ceiling {
-// 						let texture = self.textures.get(code, x, false);
-// 						let tex_y = (y * 4) as usize;
-// 						(r, g, b, a) = blend_colours(r, g, b, a, texture[tex_y + 0], texture[tex_y + 1], texture[tex_y + 2], texture[tex_y + 3]);
-// 					} else {
-// 						(r, g, b, a) = blend_colours(r, g, b, a, 0x70, 0x70, 0x70, 0xFF);
-// 					}
-// 				}
-// 			}
-
-// 			(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]) = blend_colours(r, g, b, a, buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]);
-// 		}
-
-// 		// texture the ceiling
-// 		for y in 0..(y_min) {
-// 			let ceiling = self.world.find_ceiling_intersection(origin_x, origin_y, direction, y, column);
-// 			let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
-
-// 			if let raycast::TextureCode::Ceiling(code, x, y) = ceiling {
-// 				let texture = self.textures.get(code, x, false);
-// 				let tex_y = (y * 4) as usize;
-
-// 				(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]) = (texture[tex_y + 0], texture[tex_y + 1], texture[tex_y + 2], texture[tex_y + 3]);
-// 			} else {
-// 				(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]) = (0x70, 0x70, 0x70, 0xFF);
-// 			}
-// 		}
-
-// 		// texture the floor
-// 		for y in (y_max + 1)..consts::PROJECTION_PLANE_HEIGHT {
-// 			let floor = self.world.find_floor_intersection(origin_x, origin_y, direction, y, column);
-// 			let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
-
-// 			if let raycast::TextureCode::Floor(code, x, y) = floor {
-// 				let texture = self.textures.get(code, x, false);
-// 				let tex_y = (y * 4) as usize;
-
-// 				(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]) = (texture[tex_y + 0], texture[tex_y + 1], texture[tex_y + 2], texture[tex_y + 3]);
-// 			} else {
-// 				(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]) = (0x70, 0x70, 0x70, 0xFF);
-// 			}
-// 		}
-// 	}
-
-// 	fn draw_background(&self, buf: &mut[u8]) {
-
-// 		for y in 0..consts::PROJECTION_PLANE_HEIGHT / 2 {
-// 			for x in 0..consts::PROJECTION_PLANE_WIDTH {
-// 				let idx: usize = 4 * (x + y * consts::PROJECTION_PLANE_WIDTH) as usize;
-// 				buf[idx + 0] = 0x38;
-// 				buf[idx + 1] = 0x38;
-// 				buf[idx + 2] = 0x38;
-// 				buf[idx + 3] = 0xFF; // alpha channel				
-// 			}
-// 		}
-
-// 		for y in consts::PROJECTION_PLANE_HEIGHT / 2..consts::PROJECTION_PLANE_HEIGHT {
-// 			for x in 0..consts::PROJECTION_PLANE_WIDTH {
-// 				let idx: usize = 4 * (x + y * consts::PROJECTION_PLANE_WIDTH) as usize;
-// 				buf[idx + 0] = 0x70;
-// 				buf[idx + 1] = 0x70;
-// 				buf[idx + 2] = 0x70;
-// 				buf[idx + 3] = 0xFF; // alpha channel
-// 			}
-// 		}
-// 	}
-
-// 	pub fn render(&mut self, buf: &mut[u8]) {
-// 		// self.draw_background(buf);
-
-// 		// theta is the direction player is facing
-// 		// need to start out sweep 30 degrees to the left
-// 		let mut angle = if self.player.rotation < trig::ANGLE_30 {
-// 			self.player.rotation - trig::ANGLE_30 + trig::ANGLE_360
-// 		} else {
-// 			self.player.rotation - trig::ANGLE_30
-// 		};
-
-// 		// ray casting uses fixed point notation, so convert player coordinates to fixed point
-// 		let origin_x = self.player.x.to_fp();
-// 		let origin_y = self.player.y.to_fp();
-
-// 		// sweep of the rays will be through 60 degrees
-// 		for sweep in 0..trig::ANGLE_60 {
-// 			let slices = self.world.find_wall_intersections(origin_x, origin_y, angle);
-// 			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_wall_column(buf, origin_x, origin_y, angle, sweep, &mut parameters);
-
-// 			angle += 1;
-// 			if angle >= trig::ANGLE_360 {
-// 				angle -= trig::ANGLE_360;
-// 			}
-// 		}
-// 	}
-// }

+ 286 - 33
engine/src/render.rs

@@ -1,25 +1,59 @@
-use crate::scene::Scene;
+use crate::scene::{ Tile, Scene };
 use crate::trig;
 use serde_json;
 use shared::consts;
 use shared::fp;
 use shared::fp::{ ToFixedPoint, FromFixedPoint };
 
-enum RayCastResult {
-	OutOfBounds,
-	Surface(Intersection),
+struct Colour {
+	r: u8,
+	g: u8,
+	b: u8,
+	a: u8,
+}
+
+impl Colour {
+	pub fn new(r: u8, g: u8, b: u8, a: u8) -> Colour {
+		Colour { r, g, b, a }
+	}
+
+	pub fn blend(self, other: &Colour) -> Colour {
+		let (r, g, b, a) = Colour::blend_colours(self.r, self.g, self.b, self.a, other.r, other.g, other.b, other.a);
+		Colour { r, g, b, a }
+	}
+
+	pub fn tuple(&self) -> (u8, u8, u8, u8) {
+		(self.r, self.g, self.b, self.a)
+	}
+
+	fn alpha_blend(c1: f64, a1: f64, c2: f64, a2: f64, ao: f64) -> f64 {
+		(c1 * a1 + c2 * a2 * (1.0 - a1)) / ao
+	}
+
+	fn blend_colours(r1: u8, g1: u8, b1: u8, a1: u8, r2:u8, g2:u8, b2:u8, a2:u8) -> (u8, u8, u8, u8) {
+		let fa1 = a1 as f64 / 255.0;
+		let fa2 = a2 as f64 / 255.0;
+		let fao = Colour::alpha_blend(1.0, fa1, 1.0, fa2, 1.0);
+
+		let r = Colour::alpha_blend(r1 as f64, fa1, r2 as f64, fa2, fao) as u8;
+		let g = Colour::alpha_blend(g1 as f64, fa1, g2 as f64, fa2, fao) as u8;
+		let b = Colour::alpha_blend(b1 as f64, fa1, b2 as f64, fa2, fao) as u8;
+		let a = (255.0 * fao) as u8;
+
+		(r, g, b, a)
+	}
 }
 
 struct Intersection {
 	x: i32,
 	y: i32,
 	dist: i32,
-	texture: u8,
+	texture: u32,
 	reverse: bool,
 }
 
 impl Intersection {
-	pub fn new(x: i32, y: i32, dist:i32, texture: u8, reverse: bool) -> Intersection {
+	pub fn new(x: i32, y: i32, dist:i32, texture: u32, reverse: bool) -> Intersection {
 		Intersection { x, y, dist, texture, reverse }
 	}
 }
@@ -77,6 +111,10 @@ impl Camera {
 		self.angle
 	}
 
+	pub fn horizon(&self) -> i32 {
+		self.horizon
+	}
+
 	pub fn from_json(json: &serde_json::Value) -> Result<Camera, &'static str> {
 		let x = json["x"].as_i64().unwrap() as i32;
 		let y = json["y"].as_i64().unwrap() as i32;
@@ -86,11 +124,230 @@ impl Camera {
 	}
 }
 
+struct RayCaster {}
+
+impl RayCaster {
+	fn new() -> RayCaster {
+		RayCaster {}
+	}
+	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
+		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);
+			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;
+		}
+
+		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.y_wall(grid_x, grid_y) {
+				Tile::Wall(wall) => {
+					let world_x  = x.to_i32() & (consts::TILE_SIZE - 1);
+					let world_y  = y.to_i32() & (consts::TILE_SIZE - 1);
+					let distance = fp::mul(fp::sub(y, origin_y), trig::isin(direction)).abs();
+					let texture  = wall.texture;
+					let intersection = Intersection::new(world_x, world_y, distance, texture, flipped);
+					intersects.push(intersection);
+				},
+				Tile::OutOfBounds => break,
+				Tile::Empty => {}
+			}
+
+			x = fp::add(x, step_x);
+			y = fp::add(y, step_y);
+		}
+
+
+		intersects
+	}
+
+	fn find_vertical_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
+		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;
+			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;
+		};
+
+		if direction == trig::ANGLE_90 || direction == trig::ANGLE_270 {
+			return intersects;
+		}
+
+		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.y_wall(grid_x, grid_y) {
+				Tile::Wall(wall) => {
+					let world_x  = x.to_i32() & (consts::TILE_SIZE - 1);
+					let world_y  = y.to_i32() & (consts::TILE_SIZE - 1);
+					let distance = fp::mul(fp::sub(x, origin_x), trig::icos(direction)).abs();
+					let texture  = wall.texture;
+					let intersection = Intersection::new(world_x, world_y, distance, texture, flipped);
+					intersects.push(intersection);
+				},
+				Tile::OutOfBounds => break,
+				Tile::Empty => {}
+			}
+
+			x = fp::add(x, step_x);
+			y = fp::add(y, step_y);
+		}
+
+		intersects
+	}
+
+	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;
+			}
+		}
+
+		while i < hintersects.len() {			
+			intersects.push(hintersects[i]);
+			i += 1;
+		}
+
+		while j < vintersects.len() {
+			intersects.push(vintersects[j]);
+			j += 1;
+		}
+
+		intersects
+	}
+}
+
+struct TextureMap {
+	texture_width: usize,
+	texture_height: usize,
+	texture_size: usize,
+	num_textures: usize,
+	textures: Vec<u8>
+}
+
+impl TextureMap {
+	pub fn new(texture_width: usize, texture_height: usize, textures: Vec<u8>) -> TextureMap {
+		let texture_size = texture_width * texture_height;
+		let num_textures = textures.len() / (texture_size * 4);
+		TextureMap { texture_width, texture_height, texture_size, num_textures, textures }
+	}
+
+	pub fn empty() -> TextureMap {
+		TextureMap { texture_width: 0, texture_height: 0, texture_size: 0, num_textures: 0, textures: vec![] }
+	}
+
+	pub fn get(&self, code: u32, column: i32, flipped: bool) -> &[u8] {
+		let column = if flipped { self.texture_width - 1 - column as usize } else { column as usize };
+		let pos: usize = (self.texture_size * code as usize + column as usize * self.texture_width) * 4 as usize;
+		&self.textures[pos..pos + self.texture_size]
+	}
+}
+
+struct RenderParameters<'a> {
+	texture: &'a [u8],
+	step: f64,
+	wall_height: i32,
+	tex_pos: f64,
+	y_min: i32,
+	y_max: i32,
+}
+
+impl RenderParameters<'_> {
+	pub fn new(texture: &[u8], step: f64, wall_height: i32, tex_pos: f64, y_min: i32, y_max: i32,) -> RenderParameters {
+		RenderParameters { texture, step, wall_height, tex_pos, y_min, y_max }
+	}
+}
+
 pub struct Renderer {
-	
+	raycaster: RayCaster,
+	textures: TextureMap,
 }
 
 impl Renderer {
+	pub fn new(raycaster: RayCaster, textures: TextureMap) -> Renderer {
+		Renderer{ raycaster, textures }
+	}
+
+	pub fn render_column(&self, buf: &mut[u8], column: i32, parameters: &Vec<RenderParameters>) {
+		let y_min = parameters[0].y_min;
+		let y_max = parameters[0].y_max;
+
+		for y in y_min..=y_max {
+			let pixel = Colour::new(r, g, b, a);
+			
+			let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
+
+			for intersect in parameters.iter_mut() {
+				if y < intersect.y_min || y > intersect.y_max { break; }
+				let tex_y = (intersect.tex_pos.clamp(0.0, 63.0) as usize) * 4;
+				intersect.step();
+				if a >= 255 { continue; }
+				(r, g, b, a) = blend_colours(r, g, b, a, intersect.texture[tex_y + 0], intersect.texture[tex_y + 1], intersect.texture[tex_y + 2], intersect.texture[tex_y + 3]);
+			}
+
+			(buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]) = blend_colours(r, g, b, a, buf[idx + 0], buf[idx + 1], buf[idx + 2], buf[idx + 3]);
+		}
+	}
+
 	pub fn render(&self, buf: &mut[u8], scene: &Scene, camera: &Camera) {
 		// angle is the direction camera is facing
 		// need to start out sweep 30 degrees to the left
@@ -106,29 +363,25 @@ impl Renderer {
 
 		// sweep of the rays will be through 60 degrees
 		for sweep in 0..trig::ANGLE_60 {
-			println!("{}", trig::fisheye_correction(sweep));
-		// 	let slices = self.world.find_wall_intersections(origin_x, origin_y, angle);
-		// 	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, 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_wall_column(buf, origin_x, origin_y, angle, sweep, &mut parameters);
+			let intersects = self.raycaster.find_wall_intersections(origin_x, origin_y, angle, scene);
+			if intersects.len() <= 0 { continue; }
+			let mut parameters: Vec<RenderParameters> = Vec::new();
+			parameters.reserve(intersects.len());
+
+			// for each slice, get a reference to its texture and figure out how
+			// it should be drawn
+			let parameters = intersects.iter().map(|intersect| {
+				let dist = fp::div(intersect.dist, trig::fisheye_correction(sweep)).to_i32();
+				let wall_height: i32 = trig::wall_height(dist);
+				let y_min = std::cmp::max(0, camera.horizon() - wall_height / 2);
+				let y_max = std::cmp::min(consts::PROJECTION_PLANE_HEIGHT - 1, camera.horizon() + wall_height / 2);
+				let step: f64 = consts::TEXTURE_HEIGHT as f64 / wall_height as f64;
+				let texture = self.textures.get(intersect.texture, 0, intersect.reverse);
+				let tex_pos: f64 = (y_min as f64 - camera.horizon() as f64 + wall_height as f64 / 2.0) * step;
+				RenderParameters::new(texture, step, wall_height, tex_pos, y_min, y_max)
+			}).collect();
+
+			self.render_column(buf, sweep, &mut parameters);
 
 			angle += 1;
 			if angle >= trig::ANGLE_360 {
@@ -137,9 +390,9 @@ impl Renderer {
 		}
 	}
 
-	pub fn from_json(json: &serde_json::Value) -> Result<Renderer, &'static str> {
-		Ok(Renderer {})
-	}
+	// pub fn from_json(_json: &serde_json::Value) -> Result<Renderer, &'static str> {
+	// 	Ok(Renderer::new())
+	// }
 }
 
 // struct Colour {

+ 1 - 1
engine/src/scene.rs

@@ -1,7 +1,7 @@
 use serde_json;
 
 pub struct TextureTile {
-	texture: u32,
+	pub texture: u32,
 }
 
 pub enum Tile {