From b3eb24d329a49da1a72c3d711d035e8494aba68e Mon Sep 17 00:00:00 2001 From: Fabien Freling Date: Sat, 21 Mar 2020 19:21:57 +0100 Subject: [PATCH] compute intersections --- src/engine.rs | 135 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 24 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 7d3cb7b..b3a03c4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,32 +3,52 @@ use piston_window::*; use std::f64::consts::*; -#[derive(PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] struct Position { x: f64, y: f64, } -struct Player { - pos: Position, - angle: f64, // radian or degree +impl Position { + pub fn distance(&self, other: Position) -> f64 { + ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt() + } + + pub fn distance_sqr(&self, other: Position) -> f64 { + (self.x - other.x).powi(2) + (self.y - other.y.powi(2)) + } } +type Degree = f64; +type Radian = f64; + +struct Player { + pos: Position, + angle: Degree, +} + +#[derive(Copy, Clone, PartialEq)] pub enum Tile { Empty, Wall, } pub struct Level { - pub width: u16, - pub height: u16, + pub width: usize, + pub height: usize, pub tiles: Vec, } +impl Level { + pub fn contains(&self, pos: Position) -> bool { + 0.0 <= pos.x && pos.x <= self.width as f64 && 0.0 <= pos.y && pos.y <= self.height as f64 + } +} + pub struct Engine { w: f64, h: f64, - horiz_fov: f64, + horiz_fov: Degree, player: Player, level: Level, } @@ -51,25 +71,77 @@ impl Engine { } } - fn closest_point(pos: &Position, angle: f64) -> (Tile, Position) { - // First let's find the closest intersections with the grid - let dx = angle.cos(); - let x_dist = if dx > 0.0 { 1.0 - pos.x.fract() } else { pos.x.fract() }; - let x_relative_dist = x_dist / dx.abs(); - let dy = angle.sin(); - let y_dist = if dy > 0.0 { 1.0 - pos.y.fract() } else { pos.y.fract() }; - let y_relative_dist = y_dist / dy.abs(); - if x_relative_dist > y_relative_dist { - // first grid hit is horizontal line - } else { - // first grid hit is vertical line + fn closest_point(level: &Level, pos: &Position, angle: Radian) -> (Tile, Position) { + let (y_step, x_step) = match angle { + 0.0 => (0.0, std::f64::INFINITY), + 90.0 => (std::f64::INFINITY, 0.0), + 180.0 => (0.0, std::f64::NEG_INFINITY), + 270.0 => (std::f64::NEG_INFINITY, 0.0), + x if (0.0..(PI * 0.5)).contains(&x) => (angle.tan(), ((PI / 2.0) - angle).tan()), + x if ((PI * 0.5)..PI).contains(&x) => ((PI - x).tan(), -((x - (PI / 2.0)).tan())), + x if (PI..(PI * 1.5)).contains(&x) => (-((x - PI).tan()), -(((PI * 1.5) - x).tan())), + x if ((PI * 1.5)..(PI * 2.0)).contains(&x) => (((PI * 2.0) - x).tan(), -((x - (PI * 1.5)).tan())), + _ => panic!("Invalid angle value {}.", angle), + }; + + let (x_remain, y_remain) = match (x_step, y_step) { + (x, y) if x >= 0.0 && y >= 0.0 => (1.0 - pos.x.fract(), 1.0 - pos.y.fract()), + (x, y) if x <= 0.0 && y >= 0.0 => (-pos.x.fract(), 1.0 - pos.y.fract()), + (x, y) if x >= 0.0 && y <= 0.0 => (1.0 - pos.x.fract(), -pos.y.fract()), + (x, y) if x <= 0.0 && y <= 0.0 => (-pos.x.fract(), -pos.y.fract()), + _ => panic!("Invalid steps"), + }; + + let x_dist_factor = x_remain / x_step; + let y_dist_factor = y_remain / y_step; + + let mut x_candidate = Position { + x: pos.x + x_remain, // x_remain = x_step * x_dist_factor + y: pos.y + y_step * x_dist_factor, + }; + let mut y_candidate = Position { + x: pos.x + x_step * y_dist_factor, + y: pos.y + y_remain, // y_remain = y_step * y_dist_factor + }; + + let mut next_point: Position = *pos; + let mut tile = Tile::Empty; + + while tile == Tile::Empty && level.contains(next_point) { + if next_point.distance(x_candidate) < next_point.distance(y_candidate) { + next_point = x_candidate; + x_candidate = Position { + x: x_candidate.x + x_step.signum(), + y: x_candidate.y + y_step, + }; + } else { + next_point = y_candidate; + y_candidate = Position { + x: y_candidate.x + x_step, + y: y_candidate.y + y_step.signum(), + }; + } + + tile = if next_point.x.fract() == 0.0 { + let x_index = (next_point.x.trunc() + x_step.signum()) as usize; + assert!(x_index < level.width); + let y_index = next_point.y.trunc() as usize; + assert!(y_index < level.height); + let index: usize = x_index + y_index * level.width; + level.tiles[index] + } else { + let x_index = next_point.x.trunc() as usize; + assert!(x_index < level.width); + let y_index = (next_point.y.trunc() + y_step.signum()) as usize; + assert!(y_index < level.height); + let index: usize = x_index + y_index * level.width; + level.tiles[index] + }; } - // see Game Engine Black Book - let xstep = angle.tan(); - let ystep = ((PI / 2.0) - angle).tan(); + assert!(tile != Tile::Empty); - (Tile::Empty, Position {x: 2., y: 2.}) + (tile, next_point) } pub fn render(&mut self, context: Context, graphics: &mut G2d) { @@ -99,7 +171,22 @@ impl Engine { let width = self.w as i32; for n in 0..width { // cast a ray - let (tile, pos) = Engine::closest_point(&self.player.pos, ray_angle); + let ray_radian = ray_angle.to_radians(); + let (tile, pos) = Engine::closest_point(&self.level, &self.player.pos, ray_radian); + let distance = self.player.pos.distance(pos); + let player_space_distance = (self.player.pos.x - pos.x).abs() * ray_radian.cos() + - (self.player.pos.y - pos.y).abs() * ray_radian.sin(); + if tile == Tile::Wall { + println!("ray: {}, wall at {:?}, distance: {}", n, pos, distance); + let wall_height = (self.h / (distance * 3.0)).min(self.h); + let wall_color = [0.9, 0.2, 0.2, 1.0]; + println!("wall height: {}", wall_height); + rectangle(wall_color, + [n as f64, (self.h - wall_height) / 2.0, (n + 1) as f64, wall_height], + context.transform, + graphics); + + }; // see what wall it hits // compute wall height // draw wall portion