2017-10-22 22:22:49 +02:00
|
|
|
extern crate piston_window;
|
|
|
|
use piston_window::*;
|
2020-03-12 17:54:31 +01:00
|
|
|
|
|
|
|
use std::f64::consts::*;
|
2017-10-22 22:22:49 +02:00
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
2017-10-22 22:22:49 +02:00
|
|
|
struct Position {
|
|
|
|
x: f64,
|
|
|
|
y: f64,
|
|
|
|
}
|
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
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;
|
|
|
|
|
2020-03-31 15:14:12 +02:00
|
|
|
#[derive(Debug)]
|
2017-10-22 22:22:49 +02:00
|
|
|
struct Player {
|
|
|
|
pos: Position,
|
2020-03-21 19:21:57 +01:00
|
|
|
angle: Degree,
|
2017-10-22 22:22:49 +02:00
|
|
|
}
|
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
#[derive(Copy, Clone, PartialEq)]
|
2020-03-12 17:54:31 +01:00
|
|
|
pub enum Tile {
|
|
|
|
Empty,
|
|
|
|
Wall,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Level {
|
2020-03-21 19:21:57 +01:00
|
|
|
pub width: usize,
|
|
|
|
pub height: usize,
|
2020-03-12 17:54:31 +01:00
|
|
|
pub tiles: Vec<Tile>,
|
|
|
|
}
|
|
|
|
|
2020-03-31 15:14:12 +02:00
|
|
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
|
|
pub enum Movement {
|
|
|
|
Forward,
|
|
|
|
Backward,
|
|
|
|
TurnLeft,
|
|
|
|
TurnRight,
|
|
|
|
}
|
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-22 22:22:49 +02:00
|
|
|
pub struct Engine {
|
|
|
|
w: f64,
|
|
|
|
h: f64,
|
2020-03-21 19:21:57 +01:00
|
|
|
horiz_fov: Degree,
|
2017-10-22 22:22:49 +02:00
|
|
|
player: Player,
|
2020-03-12 17:54:31 +01:00
|
|
|
level: Level,
|
2020-03-31 15:14:12 +02:00
|
|
|
inputs: Vec<Movement>,
|
2017-10-22 22:22:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Engine {
|
|
|
|
pub fn new(size: Size) -> Engine {
|
|
|
|
Engine {
|
|
|
|
w: size.width as f64,
|
|
|
|
h: size.height as f64,
|
|
|
|
horiz_fov: 90.,
|
|
|
|
player: Player {
|
|
|
|
pos: Position { x: 2., y: 2. },
|
2020-03-31 15:14:12 +02:00
|
|
|
angle: 90.,
|
2020-03-12 17:54:31 +01:00
|
|
|
},
|
|
|
|
level: Level {
|
|
|
|
width: 0,
|
|
|
|
height: 0,
|
|
|
|
tiles: vec![],
|
|
|
|
},
|
2020-03-31 15:14:12 +02:00
|
|
|
inputs: [].to_vec(),
|
2017-10-22 22:22:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
fn closest_point(level: &Level, pos: &Position, angle: Radian) -> (Tile, Position) {
|
2020-03-31 15:14:12 +02:00
|
|
|
assert!((0.0..(PI * 2.0)).contains(&angle));
|
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
let (y_step, x_step) = match angle {
|
2020-03-31 15:14:12 +02:00
|
|
|
x if x == 0.0 => (0.0, std::f64::INFINITY),
|
|
|
|
x if x == PI * 0.5 => (std::f64::INFINITY, 0.0),
|
|
|
|
x if x == PI => (0.0, std::f64::NEG_INFINITY),
|
|
|
|
x if x == PI * 1.5 => (std::f64::NEG_INFINITY, 0.0),
|
2020-03-21 19:21:57 +01:00
|
|
|
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]
|
|
|
|
};
|
2020-03-12 17:54:31 +01:00
|
|
|
}
|
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
assert!(tile != Tile::Empty);
|
2020-03-12 17:54:31 +01:00
|
|
|
|
2020-03-21 19:21:57 +01:00
|
|
|
(tile, next_point)
|
2018-01-16 01:11:46 +01:00
|
|
|
}
|
|
|
|
|
2017-10-22 22:22:49 +02:00
|
|
|
pub fn render(&mut self, context: Context, graphics: &mut G2d) {
|
|
|
|
|
|
|
|
clear([1.0; 4], graphics);
|
|
|
|
|
|
|
|
// Ceiling
|
|
|
|
let ceiling_color = [0.3, 0.3, 0.3, 1.0];
|
|
|
|
rectangle(ceiling_color,
|
|
|
|
[0.0, 0.0, self.w, self.h / 2.0],
|
|
|
|
context.transform,
|
|
|
|
graphics);
|
|
|
|
|
|
|
|
// Floor
|
|
|
|
let floor_color = [0.5, 0.5, 0.5, 1.0];
|
|
|
|
rectangle(floor_color,
|
|
|
|
[0.0, self.h / 2.0, self.w, self.h / 2.0],
|
|
|
|
context.transform,
|
|
|
|
graphics);
|
|
|
|
|
|
|
|
let left = self.player.angle + (self.horiz_fov / 2.0);
|
|
|
|
let right = self.player.angle - (self.horiz_fov / 2.0);
|
2020-03-31 15:14:12 +02:00
|
|
|
let step = self.horiz_fov / self.w;
|
2017-10-22 22:22:49 +02:00
|
|
|
let mut ray_angle = left;
|
|
|
|
let width = self.w as i32;
|
|
|
|
for n in 0..width {
|
2020-03-31 15:14:12 +02:00
|
|
|
let ray_angle = ((left - (n as f64) * step) + 360.0) % 360.0;
|
2020-03-21 19:21:57 +01:00
|
|
|
let ray_radian = ray_angle.to_radians();
|
2020-03-31 15:14:12 +02:00
|
|
|
//println!("degree: {} -> radian: {}", ray_angle, ray_radian);
|
2020-03-21 19:21:57 +01:00
|
|
|
let (tile, pos) = Engine::closest_point(&self.level, &self.player.pos, ray_radian);
|
|
|
|
let distance = self.player.pos.distance(pos);
|
2020-03-31 15:14:12 +02:00
|
|
|
let player_space_distance = (self.player.pos.x - pos.x).abs() * self.player.angle.to_radians().cos()
|
|
|
|
- (self.player.pos.y - pos.y).abs() * self.player.angle.to_radians().sin();
|
2020-03-21 19:21:57 +01:00
|
|
|
if tile == Tile::Wall {
|
2020-03-31 15:14:12 +02:00
|
|
|
//println!("ray: {}, wall at {:?}, distance: {}", n, pos, distance);
|
2020-03-21 19:21:57 +01:00
|
|
|
let wall_height = (self.h / (distance * 3.0)).min(self.h);
|
2020-03-31 15:14:12 +02:00
|
|
|
let wall_color = [0.2, 0.2, 0.9, 1.0];
|
|
|
|
//println!("wall height: {}", wall_height);
|
2020-03-21 19:21:57 +01:00
|
|
|
rectangle(wall_color,
|
|
|
|
[n as f64, (self.h - wall_height) / 2.0, (n + 1) as f64, wall_height],
|
|
|
|
context.transform,
|
|
|
|
graphics);
|
|
|
|
|
|
|
|
};
|
2017-10-22 22:22:49 +02:00
|
|
|
}
|
|
|
|
}
|
2020-03-12 17:54:31 +01:00
|
|
|
|
|
|
|
pub fn load_level(&mut self, level: Level) {
|
|
|
|
self.level = level;
|
|
|
|
}
|
2020-03-31 15:14:12 +02:00
|
|
|
|
|
|
|
pub fn add_movement(&mut self, movement: Movement) {
|
|
|
|
self.inputs.push(movement);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update(&mut self, dt: f64) {
|
|
|
|
for input in &self.inputs {
|
|
|
|
match input {
|
|
|
|
Movement::Forward => {
|
|
|
|
self.player.pos.x += self.player.angle.to_radians().cos() * dt;
|
|
|
|
self.player.pos.y += self.player.angle.to_radians().sin() * dt;
|
|
|
|
}
|
|
|
|
Movement::Backward => {
|
|
|
|
self.player.pos.x -= self.player.angle.to_radians().cos() * dt;
|
|
|
|
self.player.pos.y -= self.player.angle.to_radians().sin() * dt;
|
|
|
|
},
|
|
|
|
Movement::TurnLeft => {
|
|
|
|
self.player.angle += 90.0 * dt;
|
|
|
|
self.player.angle = (self.player.angle + 360.0) % 360.0;
|
|
|
|
}
|
|
|
|
Movement::TurnRight => {
|
|
|
|
self.player.angle -= 90.0 * dt;
|
|
|
|
self.player.angle = (self.player.angle + 360.0) % 360.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.inputs.clear();
|
|
|
|
|
|
|
|
println!("player: {:?}", &self.player);
|
|
|
|
}
|
2017-10-22 22:22:49 +02:00
|
|
|
}
|
2018-01-16 01:11:46 +01:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn closest_point() {
|
|
|
|
let origin = super::Position { x: 2.2, y: 2.3 };
|
|
|
|
let angle = 0.;
|
|
|
|
let closest = Engine::closest_point(origin, angle);
|
|
|
|
assert_eq!(closest, super::Position { x: 3.0, y: 2.3 });
|
|
|
|
}
|
|
|
|
}
|