Kaynağa Gözat

Some refactoring. Player movement added

Gary Munnelly 2 yıl önce
ebeveyn
işleme
60ec305f69
5 değiştirilmiş dosya ile 274 ekleme ve 70 silme
  1. 62 0
      src/engine.rs
  2. 91 45
      src/lib.rs
  3. 33 11
      src/raycast.rs
  4. 1 0
      webapp/index.html
  5. 87 14
      webapp/index.js

+ 62 - 0
src/engine.rs

@@ -0,0 +1,62 @@
+use crate::consts;
+use crate::raycast::{ Player, World };
+use crate::trig;
+
+pub struct Cluiche {
+	world: World,
+	player: Player,
+}
+
+impl Cluiche {
+	pub fn new() -> Cluiche {
+		let world = World::new(7, 7, "WHHHHHWVOOOOOVVOOOOOVVOOOOOVVOOOOOVVOOOOOVWHHHHHW").unwrap();
+		let player = Player::new(128, 128, 0, 5, 5);
+		Cluiche { world, player }
+	}
+	fn move_player(&mut self) {
+
+	}
+
+	fn draw_wall_column(&self, buf: &mut[u8], column: i32, dist: f64) {
+		// get wall texture, draw into column
+		let wall_height: i32 = consts::WALL_HEIGHT_SCALE_FACTOR / dist.max(1.0) as i32;
+
+		let y_min = std::cmp::max(0, (200 - wall_height) / 2);
+		let y_max = std::cmp::min(200 - 1, y_min + wall_height);
+
+		for y in y_min..=y_max {
+			let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
+			buf[idx + 0] = 0xFF;
+			buf[idx + 1] = 0x00;
+			buf[idx + 2] = 0x00;
+			buf[idx + 3] = 0xFF; // alpha channel
+		}
+	}
+
+	fn render(&self, buf: &mut[u8]) {
+		// draw a grey background that will represent the ceiling and floor
+		for x in &mut *buf { *x = 128; }
+
+		// 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
+		};
+
+		// sweep of the rays will be through 60 degrees
+		for sweep in 0..trig::ANGLE_60 {
+			let hdist = self.world.find_vertical_intersect(self.player.x, self.player.y, angle);		
+			let vdist = self.world.find_horizontal_intersect(self.player.x, self.player.y, angle);
+			let dist = hdist.min(vdist) / trig::fisheye_correction(sweep);
+
+			self.draw_wall_column(buf, sweep, dist);
+
+			angle += 1;
+			if angle >= trig::ANGLE_360 {
+				angle -= trig::ANGLE_360;
+			}
+		}
+	}
+}

+ 91 - 45
src/lib.rs

@@ -1,10 +1,11 @@
 extern crate web_sys;
 use wasm_bindgen::prelude::*;
 
-mod utils;
 mod consts;
 mod trig;
+mod utils;
 mod raycast;
+mod engine;
 
 macro_rules! log {
 	( $( $t:tt )* ) => {
@@ -12,53 +13,98 @@ macro_rules! log {
 	}
 }
 
-fn draw_wall_column(buf: &mut[u8], column: i32, dist: f64) {
-	// get wall texture, draw into column
-	let wall_height: i32 = consts::WALL_HEIGHT_SCALE_FACTOR / dist.max(1.0) as i32;
+#[wasm_bindgen]
+pub struct Cluiche {
+	world: raycast::World,
+	player: raycast::Player,
+}
 
-	let y_min = std::cmp::max(0, (200 - wall_height) / 2);
-	let y_max = std::cmp::min(200 - 1, y_min + wall_height);
+#[wasm_bindgen]
+impl Cluiche {
+	pub fn new() -> Cluiche {
+		let world = raycast::World::new(7, 7, "WHHHHHWVOOOOOVVOOOOOVVOOOOOVVOOOOOVVOOOOOVWHHHHHW").unwrap();
+		let player = raycast::Player::new(160, 160, 0, 5, 5);
+		Cluiche { world, player }
+	}
+	
+	pub fn player_forward(&mut self) {
+		let dx: f64 = trig::cos(self.player.rotation) * self.player.move_speed as f64;
+		let dy: f64 = trig::sin(self.player.rotation) * self.player.move_speed as f64;
+		self.player.pos(self.player.x + dx as i32, self.player.y + dy as i32);
+		log!("({}, {}) -> {}", self.player.x, self.player.y, self.player.rotation);
+	}
 
-	for y in y_min..=y_max {
-		let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
-		buf[idx + 0] = 0xFF;
-		buf[idx + 1] = 0x00;
-		buf[idx + 2] = 0x00;
-		buf[idx + 3] = 0xFF; // alpha channel
+	pub fn player_back(&mut self) {
+		let dx: f64 = trig::cos(self.player.rotation) * self.player.move_speed as f64;
+		let dy: f64 = trig::sin(self.player.rotation) * self.player.move_speed as f64;
+		self.player.pos(self.player.x - dx as i32, self.player.y - dy as i32);
+		log!("({}, {}) -> {}", self.player.x, self.player.y, self.player.rotation);
 	}
-}
 
-#[wasm_bindgen]
-pub fn render(buf: &mut[u8], theta: i32) {
-	let world = raycast::World::new(7, 7, "WHHHHHWVOOOOOVVOOOOOVVOOOOOVVOOOOOVVOOOOOVWHHHHHW").unwrap();
-
-	// put the player in the middle of the test map
-	let player_x = 5 * consts::TILE_SIZE / 2;
-	let player_y = 5 * consts::TILE_SIZE / 2;
-
-	// draw a grey background that will represent the ceiling and floor
-	for x in &mut *buf { *x = 128; }
-
-	// theta is the direction player is facing
-	// need to start out sweep 30 degrees to the left
-	let mut angle = if theta < trig::ANGLE_30 {
-		theta - trig::ANGLE_30 + trig::ANGLE_360
-	} else {
-		theta - trig::ANGLE_30
-	};
-
-	// sweep of the rays will be through 60 degrees
-	for sweep in 0..trig::ANGLE_60 {
-		log!("{sweep}");
-		let hdist = world.find_vertical_intersect(player_x, player_y, angle);		
-		let vdist = world.find_horizontal_intersect(player_x, player_y, angle);
-		let dist = hdist.min(vdist) / trig::fisheye_correction(sweep);
-
-		draw_wall_column(buf, sweep, dist);
-
-		angle += 1;
-		if angle >= trig::ANGLE_360 {
-			angle -= trig::ANGLE_360;
+	pub fn player_strafe_left(&mut self) {
+		self.player.rotation(self.player.rotation - trig::ANGLE_90);
+		self.player_forward();
+		self.player.rotation(self.player.rotation + trig::ANGLE_90);
+		log!("({}, {}) -> {}", self.player.x, self.player.y, self.player.rotation);
+	}
+
+	pub fn player_strafe_right(&mut self) {
+		self.player.rotation(self.player.rotation + trig::ANGLE_90);
+		self.player_forward();
+		self.player.rotation(self.player.rotation - trig::ANGLE_90);
+		log!("({}, {}) -> {}", self.player.x, self.player.y, self.player.rotation);
+	}	
+
+	pub fn player_turn_left(&mut self) {
+		self.player.rotation(self.player.rotation - self.player.rotate_speed);
+		log!("({}, {}) -> {}", self.player.x, self.player.y, self.player.rotation);
+	}
+
+	pub fn player_turn_right(&mut self) {
+		self.player.rotation(self.player.rotation + self.player.rotate_speed);
+		log!("({}, {}) -> {}", self.player.x, self.player.y, self.player.rotation);
+	}
+
+	fn draw_wall_column(&self, buf: &mut[u8], column: i32, dist: f64) {
+		// get wall texture, draw into column
+		let wall_height: i32 = consts::WALL_HEIGHT_SCALE_FACTOR / dist.max(1.0) as i32;
+
+		let y_min = std::cmp::max(0, (200 - wall_height) / 2);
+		let y_max = std::cmp::min(200 - 1, y_min + wall_height);
+
+		for y in y_min..=y_max {
+			let idx: usize = 4 * (column + y * consts::PROJECTION_PLANE_WIDTH) as usize;
+			buf[idx + 0] = 0xFF;
+			buf[idx + 1] = 0x00;
+			buf[idx + 2] = 0x00;
+			buf[idx + 3] = 0xFF; // alpha channel
 		}
 	}
-}
+
+	pub fn render(&mut self, buf: &mut[u8]) {
+		// draw a grey background that will represent the ceiling and floor
+		for x in &mut *buf { *x = 128; }
+
+		// 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
+		};
+
+		// sweep of the rays will be through 60 degrees
+		for sweep in 0..trig::ANGLE_60 {
+			let hdist = self.world.find_vertical_intersect(self.player.x, self.player.y, angle);		
+			let vdist = self.world.find_horizontal_intersect(self.player.x, self.player.y, angle);
+			let dist = hdist.min(vdist) / trig::fisheye_correction(sweep);
+
+			self.draw_wall_column(buf, sweep, dist);
+
+			angle += 1;
+			if angle >= trig::ANGLE_360 {
+				angle -= trig::ANGLE_360;
+			}
+		}
+	}
+}

+ 33 - 11
src/raycast.rs

@@ -1,24 +1,38 @@
 use crate::trig;
 use crate::consts;
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Tile {
-	Empty,
-	Wall,
-}
-
 pub struct Player {
-	x: i32,
-	y: i32,
-	rotation: i32,
+	pub x: i32,
+	pub y: i32,
+	pub rotation: i32,
+	pub move_speed: i32,
+	pub rotate_speed: i32,
 }
 
 impl Player {
-	fn new(x: i32, y: i32, rotation: i32) -> Player {
-		Player { x, y, rotation }
+	pub fn new(x: i32, y: i32, rotation: i32, move_speed: i32, rotate_speed: i32) -> Player {
+		Player { x, y, rotation, move_speed, rotate_speed }
+	}
+
+	pub fn pos(&mut self, x: i32, y: i32) {
+		self.x = x;
+		self.y = y;
+	}
+
+	pub fn rotation(&mut self, mut rotation: i32) {
+		// ensure the input rotation is within bounds
+		while rotation >= trig::ANGLE_360 { rotation -= trig::ANGLE_360; }
+		while rotation < trig::ANGLE_0 { rotation += trig::ANGLE_360; }
+		self.rotation = rotation;
 	}
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Tile {
+	Empty,
+	Wall,
+}
+
 pub struct World {
 	tile_size: i32,
 	width: i32,
@@ -162,6 +176,7 @@ impl World {
 #[cfg(test)]
 mod test {
 	use super::*;
+	use float_cmp;
 
 	#[test]
 	fn create_new_world() {
@@ -201,4 +216,11 @@ mod test {
 		assert_eq!(world.find_vertical_intersect(64, 64, trig::ANGLE_180), 64.0);
 		assert_eq!(world.find_vertical_intersect(64, 64, trig::ANGLE_270), f64::MAX);
 	}
+
+	#[test]
+	fn cast_ray_2() {
+		let world = World::new(7, 7, "WHHHHHWVOOOOOVVOOOOOVVOOOOOVVOOOOOVVOOOOOVWHHHHHW").unwrap();
+		float_cmp::assert_approx_eq!(f64, world.find_horizontal_intersect(76, 76, 295),   0.0, epsilon = 0.00000003, ulps = 2);
+		float_cmp::assert_approx_eq!(f64, world.find_vertical_intersect(76, 76, 295),   0.0, epsilon = 0.00000003, ulps = 2);
+	}
 }

+ 1 - 0
webapp/index.html

@@ -25,6 +25,7 @@
   </head>
   <body style="text-align: center">
     <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
+    <div id="fps"></div>
     <canvas id="canvas" width="320" height="200"></canvas>
     <script src="./bootstrap.js"></script>
   </body>

+ 87 - 14
webapp/index.js

@@ -1,24 +1,97 @@
 import * as wasm from "fourteen-screws";
+import { Cluiche } from "fourteen-screws";
 
-let angle = 0;
+const cluiche = Cluiche.new();
 
-function render() {
+let canvas   = document.getElementById("canvas");
+let context  = canvas.getContext("2d");
+let keystate = {}
+
+document.addEventListener('keydown', (event) => { keystate[event.key] = true; }, false);
+document.addEventListener('keyup', (event) => { keystate[event.key] = false; }, false);
 
-	let canvas = document.getElementById("canvas");
+const fps = new class {
+	constructor() {
+		this.fps = document.getElementById("fps");
+		this.frames = [];
+		this.lastFrameTimestamp = performance.now();
+	}
 
-	if (canvas) {
-		var context = canvas.getContext("2d");
+	render () {
+		// Convert the delta time since the last frame render into a measure
+		// of frames per second
+		const now = performance.now();
+		const delta = now - this.lastFrameTimeStamp;
+		this.lastFrameTimeStamp = now;
+		const fps = 1 / delta * 1000;
 
-		if (context) {
-			context.clearRect(0, 0, 320, 200);
-			var image = context.getImageData(0, 0, 320, 200);
-			wasm.render(image.data, angle);
-			context.putImageData(image, 0, 0);	
+		// Save only the latest 100 timings.
+		this.frames.push(fps);
+		if (this.frames.length > 100) {
+			this.frames.shift();
 		}
+
+		// find the max, min, and mean of our 100 latest timings
+		let min = Infinity;
+		let max = -Infinity;
+		let sum = 0;
+
+		for (let i = 0; i < this.frames.length; i++) {
+			sum += this.frames[i];
+			min = Math.min(this.frames[i], min);
+			max = Math.max(this.frames[i], max);
+		}
+
+		let mean = sum / this.frames.length;
+
+		this.fps.textContent = `
+Frames per Second:
+	     latest = ${Math.round(fps)} |
+avg of last 100 = ${Math.round(mean)} |
+min of last 100 = ${Math.round(min)} |
+max of last 100 = ${Math.round(max)}
+`.trim();
+	}
+}
+
+function render() {
+	context.clearRect(0, 0, 320, 200);
+	var image = context.getImageData(0, 0, 320, 200);
+	cluiche.render(image.data);
+	context.putImageData(image, 0, 0);
+}
+
+function events() {
+	if (keystate['w']) {
+		cluiche.player_forward();
+	}
+
+	if (keystate['s']) {
+		cluiche.player_back();
+	}
+
+	if (keystate['a']) {
+		cluiche.player_strafe_left();
+	}
+
+	if (keystate['d']) {
+		cluiche.player_strafe_right();
 	}
-	angle++;
-	if ( angle >= 1920 ) { angle = 0; }
-	requestAnimationFrame(render);	
+
+	if (keystate['ArrowLeft']) {
+		cluiche.player_turn_left();
+	}
+
+	if (keystate['ArrowRight']) {
+		cluiche.player_turn_right();
+	}
+}
+
+function tick() {
+	fps.render();
+	events();
+	render();	
+	requestAnimationFrame(tick);	
 }
 
-requestAnimationFrame(render);
+requestAnimationFrame(tick);