浏览代码

Raycast now returning lists of slices. Passable walls implemented

Gary Munnelly 2 年之前
父节点
当前提交
e76781ab90
共有 9 个文件被更改,包括 245 次插入147 次删除
  1. 二进制
      assets/sprites/LAB/tilesheet-modified.png
  2. 112 100
      engine/src/lib.rs
  3. 74 43
      engine/src/raycast.rs
  4. 55 0
      engine/src/utils.rs
  5. 0 0
      engine/webapp/demo-level.js
  6. 二进制
      engine/webapp/favicon.ico
  7. 1 1
      engine/webapp/index.html
  8. 3 3
      engine/webapp/index.js
  9. 0 0
      img2tex/out.base64

二进制
assets/sprites/LAB/tilesheet-modified.png


+ 112 - 100
engine/src/lib.rs

@@ -60,7 +60,7 @@ pub struct Cluiche {
 #[wasm_bindgen]
 impl Cluiche {
 	pub fn new() -> Cluiche {
-		let world = raycast::World::new(13, 6, "WHHHHWHWHHHHWVOOOOVOVOOOOVVOOOOVOVOOOOVVOOOOVOOOOOOVVOOOOOOVOOOOVWHHHHWHWHHHWW").unwrap();
+		let world = raycast::World::new(13, 6, "WHHHHWHWHHHHWVOOOOVOVOOOOVVOOOOVOVOOOOVVOOOOVOXOOOOVVOOOOXOVOOOOVWHHHHWHWHHHWW").unwrap();
 		let player = raycast::Player::new(160, 160, 0, 5, 10);
 		let textures = TextureMap::empty();
 		Cluiche { world, player, textures }
@@ -93,8 +93,8 @@ impl Cluiche {
 		let grid_y = y_top / consts::TILE_SIZE;
 
 		if x1 < xp { // are we moving left
-			if self.world.is_x_wall(grid_x, grid_y) {
-				if x1 < x_left || (x1 - x_left).abs() < 28 { // we crossed the wall or we're too close
+			if let raycast::Tile::Wall(texture, 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;
 				}
@@ -102,8 +102,8 @@ impl Cluiche {
 		}
 
 		if x1 > xp { // are we moving right
-			if self.world.is_x_wall(grid_x + 1, grid_y) { // wall found in current square (right edge)
-				if x1 > x_right || (x_right - x1).abs() < 28 { // we crossed the wall or we're too close
+			if let raycast::Tile::Wall(texture, 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;
 				}
@@ -111,8 +111,8 @@ impl Cluiche {
 		}
 
 		if y1 < yp { // are we moving up			
-			if self.world.is_y_wall(grid_x, grid_y) {
-				if y1 < y_top || (y1 - y_top).abs() < 28 {
+			if let raycast::Tile::Wall(texture, 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;
 				}
@@ -120,8 +120,8 @@ impl Cluiche {
 		}
 
 		if y1 > yp { // are we moving down
-			if self.world.is_y_wall(grid_x, grid_y + 1) {
-				if y1 > y_bottom || (y_bottom - y1).abs() < 28 {
+			if let raycast::Tile::Wall(texture, 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;
 				}
@@ -141,29 +141,33 @@ impl Cluiche {
 				
 				// check region A-top left area of grid
 				if x1 < x_left + 32 { // new x position falls in left half
-					let m_code_x = self.world.is_x_wall(grid_x, grid_y - 1); // check adjacent x wall (to left)
-					let m_code_y = self.world.is_y_wall(grid_x - 1, grid_y); // check adjacent y wall (above)
-					
-					if m_code_x && 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 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;
+								}
 							}
 						}
 					}
 
-					if m_code_y && 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 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;
+								}
 							}
 						}
 					}
@@ -171,28 +175,33 @@ impl Cluiche {
 
 				// check region B-top right area
 				if x1 > x_right - 32 && hit_result == HitResult::Nothing {
-					let m_code_x = self.world.is_x_wall(grid_x + 1, grid_y - 1); // check adjacent x wall (to right)
-					let m_code_y = self.world.is_y_wall(grid_x + 1, grid_y); // check adjacent y wall (above)
-					if m_code_x && 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 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;
+								}
 							}
 						}
 					}
 
-					if m_code_y && 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 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;
+								}
 							}
 						}
 					}
@@ -201,30 +210,34 @@ impl Cluiche {
 
 			// check region C-bottom left area
 			if y1 > y_top + 32 && hit_result == HitResult::Nothing {
-				if x1 < x_left + 32 {
-					let m_code_x = self.world.is_x_wall(grid_x, grid_y + 1); // check adjacent x wall (to left)
-					let m_code_y = self.world.is_y_wall(grid_x - 1, grid_y + 1); // check adjacent y wall (below)
+				if x1 < x_left + 32 {					
 					
-					if m_code_x && 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 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;
+								}
 							}
 						}
 					}
-
-					if m_code_y && 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 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;
+								}
 							}
 						}
 					}
@@ -232,29 +245,33 @@ impl Cluiche {
 
 				// check region D-bottom right area
 				if x1 > x_right - 32 && hit_result == HitResult::Nothing {
-					let m_code_x = self.world.is_x_wall(grid_x + 1, grid_y + 1); // check adjacent x wall (to right)
-					let m_code_y = self.world.is_y_wall(grid_x + 1, grid_y + 1); // check adjacent y wall (below)
 					
-					if m_code_x && 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 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;
+								}
 							}
 						}
 					}
 
-					if m_code_y && 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;
+					// 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;
+								}
 							}
 						}
 					}
@@ -299,7 +316,7 @@ impl Cluiche {
 		self.player.rotation(self.player.rotation + self.player.rotate_speed);
 	}
 
-	fn draw_wall_column(&self, buf: &mut[u8], column: i32, slice: raycast::Slice, dist: i32) {
+	fn draw_wall_column(&self, buf: &mut[u8], column: i32, slice: &raycast::Slice, dist: i32) {
 		// get wall texture, draw into column
 		let wall_height: i32 = trig::wall_height(dist);
 
@@ -317,21 +334,15 @@ impl Cluiche {
 				let tex_y = (tex_pos as usize & (consts::TEXTURE_HEIGHT - 1)) * 4;
 				let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
 
-				buf[idx + 0] = texture[tex_y + 0] as u8;
-				buf[idx + 1] = texture[tex_y + 1] as u8;
-				buf[idx + 2] = texture[tex_y + 2] as u8;
-				buf[idx + 3] = texture[tex_y + 3]; // alpha channel
+				if texture[tex_y + 3] > 0 {
+					buf[idx + 0] = texture[tex_y + 0] as u8;
+					buf[idx + 1] = texture[tex_y + 1] as u8;
+					buf[idx + 2] = texture[tex_y + 2] as u8;
+					buf[idx + 3] = texture[tex_y + 3]; // alpha channel
+				}
+				
 				tex_pos += step;
 			}
-
-			// for y in y_min..=y_max {
-			// 	let tex_y = ((y - y_min) as f64 * scale) as usize * 4;
-			// 	let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
-			// 	buf[idx + 0] = texture[tex_y + 0] as u8;
-			// 	buf[idx + 1] = texture[tex_y + 1] as u8;
-			// 	buf[idx + 2] = texture[tex_y + 2] as u8;
-			// 	buf[idx + 3] = texture[tex_y + 3]; // alpha channel
-			// }
 		}
 	}
 
@@ -373,10 +384,11 @@ impl Cluiche {
 		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 slice = self.world.find_closest_intersect(origin_x, origin_y, angle);
+			let slices = self.world.find_closest_intersect(origin_x, origin_y, angle);
+			if slices.len() <= 0 { continue; }
+			let slice = &slices[0];
 			let dist = fp::div(slice.distance, trig::fisheye_correction(sweep));
 
 			self.draw_wall_column(buf, sweep, slice, dist.to_i32());

+ 74 - 43
engine/src/raycast.rs

@@ -3,11 +3,12 @@ use crate::consts;
 use crate::fp;
 use crate::fp::{ ToFixedPoint, FromFixedPoint };
 
+#[derive(Debug, Copy, Clone)]
 pub enum TextureCode {
-	None,
 	Wall(u8, i32, bool)
 }
 
+#[derive(Debug, Copy, Clone)]
 pub struct Slice {
 	pub texture: TextureCode,
 	pub distance: i32,
@@ -48,7 +49,8 @@ impl Player {
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Tile {
 	Empty,
-	Wall,
+	Wall(u8, bool),
+	OutOfBounds,
 }
 
 pub struct World {
@@ -71,7 +73,7 @@ impl World {
 		let y_walls: Vec<Tile> = map_str.chars()
 			.map(|c| {
 				if c == 'W' || c == 'H' {
-					Tile::Wall
+					Tile::Wall(3, false)
 				} else {
 					Tile::Empty
 				}
@@ -81,7 +83,9 @@ impl World {
 		let x_walls: Vec<Tile> = map_str.chars()
 			.map(|c| {
 				if c == 'W' || c == 'V' {
-					Tile::Wall
+					Tile::Wall(3, false)
+				} else if c == 'X' {
+					Tile::Wall(1, true)
 				} else {
 					Tile::Empty
 				}
@@ -95,119 +99,146 @@ impl World {
 		x >= 0 && x < self.width && y >= 0 && y < self.height
 	}
 
-	pub fn is_y_wall(&self, x:i32, y:i32) -> bool {
-		if !self.is_within_bounds(x, y) { return true; }
-		self.y_walls[(x + y  * self.width) as usize] == Tile::Wall
+	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 is_x_wall(&self, x:i32, y:i32) -> bool {
-		if !self.is_within_bounds(x, y) { return true; }
-		self.x_walls[(x + y  * self.width) as usize] == Tile::Wall
+	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]
 	}
 
-	fn find_horizontal_intersect(&self, origin_x: i32, origin_y: i32, direction: i32) -> Slice {
+	fn find_horizontal_intersect(&self, origin_x: i32, origin_y: i32, direction: i32) -> 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;
 
-			let hi = ((origin_y.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE + consts::TILE_SIZE).to_fp();
-			x = fp::add(origin_x, fp::mul(fp::sub(hi, origin_y), trig::itan(direction)));
-			y = hi;
+			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;
 
-			let hi = ((origin_y.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE).to_fp();
-			x = fp::add(origin_x, fp::mul(fp::sub(hi, origin_y), trig::itan(direction)));
-			y = hi;
+			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 Slice::new(TextureCode::None, consts::FP_MAX_RAY_LENGTH);
+			return slices;
+			// return Slice::new(TextureCode::None, consts::FP_MAX_RAY_LENGTH);
 		}
 
 		// Cast x axis intersect rays, build up xSlice
 
 		while self.is_within_bounds(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
-			if self.is_y_wall(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
-				return Slice::new(
-					TextureCode::Wall(3, x.to_i32() % consts::TILE_SIZE, flipped),
+			if let Tile::Wall(texture, _) = self.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, 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);
 		}
 
-		Slice::new(TextureCode::None, consts::FP_MAX_RAY_LENGTH)
+		slices
 	}
 
-	fn find_vertical_intersect(&self, origin_x: i32, origin_y: i32, direction: i32) -> Slice {
+	fn find_vertical_intersect(&self, origin_x: i32, origin_y: i32, direction: i32) -> 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
-			let vi = ((origin_x.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE + consts::TILE_SIZE).to_fp();
 			step_x = consts::FP_TILE_SIZE;
 			step_y = trig::ystep(direction);
 			
-			x = vi;
-			y = fp::add(origin_y, fp::mul(fp::sub(vi, origin_x), trig::tan(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 {
-			let vi = (((origin_x.to_i32() / consts::TILE_SIZE) * consts::TILE_SIZE)).to_fp();
-			
 			step_x = -consts::FP_TILE_SIZE;
 			step_y = trig::ystep(direction);
 			
-			x = vi; //fp::sub(vi, 1.to_fp());
-			y = fp::add(origin_y, fp::mul(fp::sub(vi, origin_x), trig::tan(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 Slice::new(TextureCode::None, consts::FP_MAX_RAY_LENGTH);
+			return slices;
 		}
 
 		// Cast y axis intersect rays, build up ySlice
 		while self.is_within_bounds(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
-			if self.is_x_wall(fp::div(x, consts::FP_TILE_SIZE).to_i32(), fp::div(y, consts::FP_TILE_SIZE).to_i32()) {
-				return Slice::new(
-					TextureCode::Wall(3, y.to_i32() % consts::TILE_SIZE, flipped),
+			if let Tile::Wall(texture, _) = self.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, 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);
 		}
 
-		Slice::new(TextureCode::None, consts::FP_MAX_RAY_LENGTH)
+		slices
 	}
 
-	pub fn find_closest_intersect(&self, origin_x: i32, origin_y: i32, direction: i32) -> Slice {
-		let hslice = self.find_horizontal_intersect(origin_x, origin_y, direction);
-		let vslice = self.find_vertical_intersect(origin_x, origin_y, direction);
+	pub fn find_closest_intersect(&self, origin_x: i32, origin_y: i32, direction: i32) -> Vec<Slice> {
+		let hslices = self.find_horizontal_intersect(origin_x, origin_y, direction);
+		let vslices = self.find_vertical_intersect(origin_x, origin_y, direction);
 		
-		if hslice.distance < vslice.distance {
-			hslice
-		} else {
-			vslice
+		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
 	}
 }
 

+ 55 - 0
engine/src/utils.rs

@@ -1,3 +1,58 @@
+use std::iter::Peekable;
+use std::cmp::Ordering;
+
+// ============================================
+// merge code by shepmaster
+// https://stackoverflow.com/a/32020190/4261231
+// ============================================
+
+pub struct MergeAscending<L, R>
+    where L: Iterator<Item = R::Item>,
+          R: Iterator,
+{
+    left: Peekable<L>,
+    right: Peekable<R>,
+}
+
+impl<L, R> MergeAscending<L, R>
+    where L: Iterator<Item = R::Item>,
+          R: Iterator,
+{
+    pub fn new(left: L, right: R) -> Self {
+        MergeAscending {
+            left: left.peekable(),
+            right: right.peekable(),
+        }
+    }
+}
+
+impl<L, R> Iterator for MergeAscending<L, R>
+    where L: Iterator<Item = R::Item>,
+          R: Iterator,
+          L::Item: Ord,
+{
+    type Item = L::Item;
+
+    fn next(&mut self) -> Option<L::Item> {
+        let which = match (self.left.peek(), self.right.peek()) {
+            (Some(l), Some(r)) => Some(l.cmp(r)),
+            (Some(_), None)    => Some(Ordering::Less),
+            (None, Some(_))    => Some(Ordering::Greater),
+            (None, None)       => None,
+        };
+
+        match which {
+            Some(Ordering::Less)    => self.left.next(),
+            Some(Ordering::Equal)   => self.left.next(),
+            Some(Ordering::Greater) => self.right.next(),
+            None                    => None,
+        }
+    }
+}
+// ============================================
+// end shepmaster code
+// ============================================
+
 pub fn set_panic_hook() {
     // When the `console_error_panic_hook` feature is enabled, we can call the
     // `set_panic_hook` function at least once during initialization, and then

文件差异内容过多而无法显示
+ 0 - 0
engine/webapp/demo-level.js


二进制
engine/webapp/favicon.ico


+ 1 - 1
engine/webapp/index.html

@@ -34,7 +34,7 @@
   </head>
   <body style="text-align: center">
     <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
-    <div id="controls">w, s, a, d = forward, back, strafe left, strafe right | &larr;, &rarr; = turn left, turn right</div>
+    <div id="controls">w, s, a, d = forward, back, strafe left, strafe right | &uarr;, &darr;, &larr;, &rarr; = forward, back, turn left, turn right</div>
     <div id="fps"></div>
     <canvas id="canvas" width="320" height="200"></canvas>
     <script src="./bootstrap.js"></script>

+ 3 - 3
engine/webapp/index.js

@@ -8,7 +8,7 @@ const cluiche = Cluiche.new();
 cluiche.load_textures(textures);
 
 let canvas   = document.getElementById("canvas");
-let context  = canvas.getContext("2d");
+let context  = canvas.getContext("2d", { willReadFrequently: true });
 let keystate = {}
 
 document.addEventListener('keydown', (event) => { keystate[event.key] = true; }, false);
@@ -66,11 +66,11 @@ function render() {
 }
 
 function events() {
-	if (keystate['w']) {
+	if (keystate['w'] || keystate['ArrowUp']) {
 		cluiche.player_forward();
 	}
 
-	if (keystate['s']) {
+	if (keystate['s'] || keystate['ArrowDown']) {
 		cluiche.player_back();
 	}
 

文件差异内容过多而无法显示
+ 0 - 0
img2tex/out.base64


部分文件因为文件数量过多而无法显示