compute intersections
This commit is contained in:
		
							parent
							
								
									5774c3e488
								
							
						
					
					
						commit
						b3eb24d329
					
				
					 1 changed files with 111 additions and 24 deletions
				
			
		
							
								
								
									
										135
									
								
								src/engine.rs
									
										
									
									
									
								
							
							
						
						
									
										135
									
								
								src/engine.rs
									
										
									
									
									
								
							| 
						 | 
					@ -3,32 +3,52 @@ use piston_window::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::f64::consts::*;
 | 
					use std::f64::consts::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Debug)]
 | 
					#[derive(Copy, Clone, PartialEq, Debug)]
 | 
				
			||||||
struct Position {
 | 
					struct Position {
 | 
				
			||||||
    x: f64,
 | 
					    x: f64,
 | 
				
			||||||
    y: f64,
 | 
					    y: f64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Player {
 | 
					impl Position {
 | 
				
			||||||
   pos: Position,
 | 
					    pub fn distance(&self, other: Position) -> f64 {
 | 
				
			||||||
   angle: f64, // radian or degree
 | 
					        ((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 {
 | 
					pub enum Tile {
 | 
				
			||||||
    Empty,
 | 
					    Empty,
 | 
				
			||||||
    Wall,
 | 
					    Wall,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Level {
 | 
					pub struct Level {
 | 
				
			||||||
    pub width: u16,
 | 
					    pub width: usize,
 | 
				
			||||||
    pub height: u16,
 | 
					    pub height: usize,
 | 
				
			||||||
    pub tiles: Vec<Tile>,
 | 
					    pub tiles: Vec<Tile>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
					pub struct Engine {
 | 
				
			||||||
    w: f64,
 | 
					    w: f64,
 | 
				
			||||||
    h: f64,
 | 
					    h: f64,
 | 
				
			||||||
    horiz_fov: f64,
 | 
					    horiz_fov: Degree,
 | 
				
			||||||
    player: Player,
 | 
					    player: Player,
 | 
				
			||||||
    level: Level,
 | 
					    level: Level,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -51,25 +71,77 @@ impl Engine {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn closest_point(pos: &Position, angle: f64) -> (Tile, Position) {
 | 
					    fn closest_point(level: &Level, pos: &Position, angle: Radian) -> (Tile, Position) {
 | 
				
			||||||
        // First let's find the closest intersections with the grid
 | 
					        let (y_step, x_step) = match angle {
 | 
				
			||||||
        let dx = angle.cos();
 | 
					            0.0 => (0.0, std::f64::INFINITY),
 | 
				
			||||||
        let x_dist = if dx > 0.0 { 1.0 - pos.x.fract() } else { pos.x.fract() };
 | 
					            90.0 => (std::f64::INFINITY, 0.0),
 | 
				
			||||||
        let x_relative_dist = x_dist / dx.abs();
 | 
					            180.0 => (0.0, std::f64::NEG_INFINITY),
 | 
				
			||||||
        let dy = angle.sin();
 | 
					            270.0 => (std::f64::NEG_INFINITY, 0.0),
 | 
				
			||||||
        let y_dist = if dy > 0.0 { 1.0 - pos.y.fract() } else { pos.y.fract() };
 | 
					            x if (0.0..(PI * 0.5)).contains(&x) => (angle.tan(), ((PI / 2.0) - angle).tan()),
 | 
				
			||||||
        let y_relative_dist = y_dist / dy.abs();
 | 
					            x if ((PI * 0.5)..PI).contains(&x) => ((PI - x).tan(), -((x - (PI / 2.0)).tan())),
 | 
				
			||||||
        if x_relative_dist > y_relative_dist {
 | 
					            x if (PI..(PI * 1.5)).contains(&x) => (-((x - PI).tan()), -(((PI * 1.5) - x).tan())),
 | 
				
			||||||
            // first grid hit is horizontal line
 | 
					            x if ((PI * 1.5)..(PI * 2.0)).contains(&x) => (((PI * 2.0) - x).tan(), -((x - (PI * 1.5)).tan())),
 | 
				
			||||||
        } else {
 | 
					            _ => panic!("Invalid angle value {}.", angle),
 | 
				
			||||||
            // first grid hit is vertical line
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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
 | 
					        assert!(tile != Tile::Empty);
 | 
				
			||||||
        let xstep = angle.tan();
 | 
					 | 
				
			||||||
        let ystep = ((PI / 2.0) - angle).tan();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (Tile::Empty, Position {x: 2., y: 2.})
 | 
					       (tile, next_point)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn render(&mut self, context: Context, graphics: &mut G2d) {
 | 
					    pub fn render(&mut self, context: Context, graphics: &mut G2d) {
 | 
				
			||||||
| 
						 | 
					@ -99,7 +171,22 @@ impl Engine {
 | 
				
			||||||
        let width = self.w as i32;
 | 
					        let width = self.w as i32;
 | 
				
			||||||
        for n in 0..width {
 | 
					        for n in 0..width {
 | 
				
			||||||
            //   cast a ray
 | 
					            //   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
 | 
					            //   see what wall it hits
 | 
				
			||||||
            //   compute wall height
 | 
					            //   compute wall height
 | 
				
			||||||
            //   draw wall portion
 | 
					            //   draw wall portion
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue