aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/camera.rs96
-rw-r--r--src/framebuffer.rs234
-rw-r--r--src/main.rs261
-rw-r--r--src/map.rs60
-rw-r--r--src/texture.rs81
-rw-r--r--src/util.rs45
-rw-r--r--src/vec2.rs52
7 files changed, 829 insertions, 0 deletions
diff --git a/src/camera.rs b/src/camera.rs
new file mode 100644
index 0000000..b158dee
--- /dev/null
+++ b/src/camera.rs
@@ -0,0 +1,96 @@
+use crate::map::{Map, MapCell};
+use crate::util::{Side, Step};
+use crate::vec2::Vec2;
+
+use minifb::{Key, Window};
+
+pub struct Intersection {
+ pub side: Side,
+ pub step: Vec2<Step>,
+ pub map_coordinates: Vec2<usize>,
+ pub wall_offset: Vec2<f64>,
+}
+
+pub struct Ray {
+ pub direction: Vec2<f64>,
+ pub intersections: Vec<Intersection>,
+}
+
+pub struct Camera {
+ pub position: Vec2<f64>,
+ pub direction: Vec2<f64>,
+ pub plane: Vec2<f64>,
+ pub height: f64,
+}
+
+impl Camera {
+ pub fn get_ray(&self, x: usize, screen_width: usize) -> Ray {
+ let camera_x: f64 = 2.0 * (x as f64) / (screen_width as f64) - 1.0;
+ Ray {
+ direction: &self.direction + &self.plane * camera_x,
+ intersections: Vec::new(),
+ }
+ }
+
+ pub fn update_position_with_keys(&mut self, delta: f64, window: &Window, world: &Map) {
+ let move_speed = delta * 5.0;
+ let rot_speed = delta * 3.0;
+
+ if window.is_key_down(Key::W) {
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position + &self.direction * move_speed).as_usize())
+ {
+ self.position += &self.direction * move_speed;
+ }
+ }
+ if window.is_key_down(Key::S) {
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position - &self.direction * move_speed).as_usize())
+ {
+ self.position -= &self.direction * move_speed;
+ }
+ }
+ if window.is_key_down(Key::A) {
+ let mut direction = self.direction.clone();
+ direction.rotate(-std::f64::consts::PI / 2.0);
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position - &direction * move_speed).as_usize())
+ {
+ self.position -= &direction * (move_speed / 1.5);
+ }
+ }
+ if window.is_key_down(Key::D) {
+ let mut direction = self.direction.clone();
+ direction.rotate(std::f64::consts::PI / 2.0);
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position - &direction * move_speed).as_usize())
+ {
+ self.position -= &direction * (move_speed / 1.5);
+ }
+ }
+ if window.is_key_down(Key::Left) {
+ self.direction.rotate(rot_speed);
+ self.plane.rotate(rot_speed);
+ }
+ if window.is_key_down(Key::Right) {
+ self.direction.rotate(-rot_speed);
+ self.plane.rotate(-rot_speed);
+ }
+ }
+}
diff --git a/src/framebuffer.rs b/src/framebuffer.rs
new file mode 100644
index 0000000..5a250d7
--- /dev/null
+++ b/src/framebuffer.rs
@@ -0,0 +1,234 @@
+use crate::texture::Font;
+use crate::map::{MapCell, Map};
+use crate::camera::{Ray, Camera};
+use crate::util::{Side, Sprite};
+use crate::vec2::Vec2;
+
+pub struct Framebuffer {
+ pub height: usize,
+ pub width: usize,
+ pub pixels: Vec<u32>,
+ pub z_buffer: Vec<f64>,
+}
+
+impl Framebuffer {
+ pub fn new(height: usize, width: usize) -> Framebuffer {
+ Framebuffer {
+ height,
+ width,
+ pixels: vec![0; height * width],
+ z_buffer: vec![f64::INFINITY; height * width],
+ }
+ }
+
+ pub fn clear(&mut self) {
+ for pixel in &mut self.pixels {
+ *pixel = 0;
+ }
+ }
+
+ pub fn clear_z_buffer(&mut self) {
+ for pixel in &mut self.z_buffer {
+ *pixel = f64::INFINITY;
+ }
+ }
+
+ pub fn draw_vertical_line(&mut self, x: usize, start: usize, stop: usize, color: u32) {
+ for row in start..stop {
+ self.pixels[row * self.width + x] = color;
+ }
+ }
+
+ pub fn write_ascii_string(&mut self, x: usize, y: usize, string: &Vec<u8>, font: &Font, color: u32) {
+ for char_index in 0..string.len() {
+ for glyph_x in 0..font.glyph_size {
+ for glyph_y in 0..font.glyph_size {
+ if font.glyphs[(font.glyph_size - 1 - glyph_y) * (font.charset_length * font.glyph_size) + glyph_x + font.glyph_size * (string[char_index] as usize)] {
+ self.pixels[((y + glyph_y) * self.width) + x + (char_index * font.glyph_size) + glyph_x] = color;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn set_pixel(&mut self, x: usize, y: usize, color: u32) {
+ if let Some(pixel) = self.pixels.get_mut(y * self.width + x) {
+ *pixel = color;
+ }
+ }
+
+ pub fn draw_wall(&mut self, camera: &Camera, x: usize, perp_wall_dist: f64, cell: &MapCell, side: &Side, ray: &Ray, world: &Map) {
+ let line_height = (self.height as f64 / perp_wall_dist) as i32;
+ if line_height < 0 {
+ self.draw_vertical_line(x, 0, self.height, 0x00FF0000);
+ return;
+ }
+ let draw_start = ((-line_height / 2 + (self.height as i32) / 2) - 1 + ((camera.height / perp_wall_dist) as i32)).max(0);
+ let draw_end = ((line_height / 2 + (self.height as i32) / 2) + 1 + ((camera.height / perp_wall_dist) as i32)).min(self.height as i32);
+
+ match cell {
+ MapCell::Wall { texture } | MapCell::ThinWall { texture, orientation: _, offset_into_cell: _, ceiling_texture: _, floor_texture: _ } => {
+ let wall_x = match &side {
+ Side::X => camera.position.y + perp_wall_dist * ray.direction.y,
+ Side::Y=> camera.position.x + perp_wall_dist * ray.direction.x,
+ }.fract();
+
+ let mut tex_x = (wall_x * (texture.width as f64)) as usize;
+ if let Side::X = side {
+ if ray.direction.x > 0.0 {
+ tex_x = texture.width - tex_x - 1;
+ }
+ } else {
+ if ray.direction.y < 0.0 {
+ tex_x = texture.width - tex_x - 1;
+ }
+ }
+ let step = (texture.height as f64) / (line_height as f64);
+ let mut tex_position = ((draw_start as f64) - (camera.height / perp_wall_dist) - (self.height as f64) / 2.0 + (line_height as f64) / 2.0) * step;
+ for y in draw_start..draw_end {
+ let tex_y = (tex_position as usize) & (texture.height - 1);
+ tex_position += step;
+ if perp_wall_dist < self.z_buffer[y as usize * self.width + x as usize] {
+ let mut color = texture.data[texture.height * tex_y + tex_x];
+ if (color & 0x00FFFFFF) != 0 {
+ if let Side::Y = side {
+ color = (color >> 1) & 8355711;
+ }
+ if let Some(MapCell::Empty { ceiling_texture: _, floor_texture: _, fog, fog_color }) = world.at(&camera.position.as_usize()) {
+ let fog_prop = (perp_wall_dist * fog).min(1.0);
+ if fog_prop > 0.0 {
+ let mut color_bytes = color.to_le_bytes();
+ let fog_bytes = fog_color.to_le_bytes();
+ color_bytes[0] = (fog_bytes[0] as f64 * fog_prop + color_bytes[0] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[1] = (fog_bytes[1] as f64 * fog_prop + color_bytes[1] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[2] = (fog_bytes[2] as f64 * fog_prop + color_bytes[2] as f64 * (1.0 - fog_prop)) as u8;
+ color = u32::from_le_bytes(color_bytes);
+ }
+ }
+ self.set_pixel(x, y as usize, color);
+ self.z_buffer[y as usize * self.width + x as usize] = perp_wall_dist;
+ }
+ }
+ }
+ },
+ MapCell::Empty { ceiling_texture: _, floor_texture: _, fog: _, fog_color: _ } => {},
+ }
+ }
+
+ pub fn draw_sprites(&mut self, camera: &Camera, sprites: &mut Vec<Sprite>, world: &Map) {
+ for sprite in sprites.into_iter() {
+ sprite.distance_from_camera = (&camera.position - &sprite.position).length();
+ }
+ for sprite in sprites {
+ let rel_position = &sprite.position - &camera.position;
+ let inverse_det = 1.0 / (camera.plane.x * camera.direction.y - camera.direction.x * camera.plane.y);
+ let transform = Vec2 {
+ x: inverse_det * (camera.direction.y * rel_position.x - camera.direction.x * rel_position.y),
+ y: inverse_det * (-camera.plane.y * rel_position.x + camera.plane.x * rel_position.y)
+ };
+ if transform.y == 0.0 {
+ continue;
+ }
+ let vertical_offset = ((sprite.vertical_offset / transform.y) + (camera.height / transform.y)) as i32;
+ let sprite_screen_x = ((self.width as f64 / 2.0) * (1.0 + transform.x / transform.y)) as i32;
+ let sprite_height = (((self.height as f64 / transform.y) as i32).abs() as f64 * sprite.scale_factor.y) as i32;
+ let sprite_width = (((self.height as f64 / transform.y) as i32).abs() as f64 * sprite.scale_factor.x) as i32;
+ let draw_start = Vec2 {
+ x: ((-sprite_width / 2) + sprite_screen_x).max(0),
+ y: ((-sprite_height / 2 + (self.height as i32) / 2) + vertical_offset).max(0),
+ };
+ let draw_end = Vec2 {
+ x: ((sprite_width / 2) + sprite_screen_x).min(self.width as i32),
+ y: ((sprite_height / 2 + (self.height as i32) / 2) + vertical_offset).min(self.height as i32),
+ };
+ for column in draw_start.x..draw_end.x {
+ let tex_x = (256 * (column - (-sprite_width / 2 + (sprite_screen_x))) * sprite.texture.width as i32 / sprite_width) as i32 / 256;
+ if transform.y > 0.0 && column >= 0 && column < self.width as i32 {
+ for y in draw_start.y..draw_end.y {
+ let d = (y - vertical_offset) * 256 - self.height as i32 * 128 + sprite_height * 128;
+ let tex_y = ((d * sprite.texture.height as i32) / sprite_height) / 256;
+ if (tex_x as usize) < sprite.texture.width && (tex_y as usize) < sprite.texture.height {
+ let mut color = sprite.texture.data[sprite.texture.width * tex_y as usize + tex_x as usize];
+ if (color & 0x00FFFFFF) != 0 {
+ if let Some(&depth) = self.z_buffer.get(y as usize * self.width + column as usize) {
+ if sprite.distance_from_camera < depth {
+ if let Some(MapCell::Empty { ceiling_texture: _, floor_texture: _, fog, fog_color }) = world.at(&camera.position.as_usize()) {
+ let fog_prop = (sprite.distance_from_camera * fog).min(1.0);
+ if fog_prop > 0.0 {
+ let mut color_bytes = color.to_le_bytes();
+ let fog_bytes = fog_color.to_le_bytes();
+ color_bytes[0] = (fog_bytes[0] as f64 * fog_prop + color_bytes[0] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[1] = (fog_bytes[1] as f64 * fog_prop + color_bytes[1] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[2] = (fog_bytes[2] as f64 * fog_prop + color_bytes[2] as f64 * (1.0 - fog_prop)) as u8;
+ color = u32::from_le_bytes(color_bytes);
+ }
+ }
+ self.set_pixel(column as usize, y as usize, color);
+ self.z_buffer[y as usize * self.width + column as usize] = sprite.distance_from_camera;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pub fn draw_floor_and_ceiling(&mut self, camera: &Camera, world: &Map) {
+ for y in 0..self.height {
+ let is_floor = y > self.height / 2;
+ let ray_dir_0 = &camera.direction - &camera.plane;
+ let ray_dir_1 = &camera.direction + &camera.plane;
+
+ let current_position = if is_floor {
+ (y as i32) - (self.height as i32) / 2
+ } else {
+ (self.height as i32) / 2 - (y as i32)
+ };
+ let camera_z = if is_floor {
+ 0.5 * self.height as f64 + camera.height
+ } else {
+ 0.5 * self.height as f64 - camera.height
+ };
+ let row_distance = camera_z / current_position as f64;
+
+ let floor_step = row_distance * (&ray_dir_1 - &ray_dir_0) / self.width as f64;
+ let mut floor = &camera.position + row_distance * &ray_dir_0;
+
+ for x in 0..self.width {
+ let cell = floor.as_usize();
+ floor += &floor_step;
+ match world.at(&cell) {
+ Some(MapCell::Empty { ceiling_texture, floor_texture, fog: _, fog_color: _ }) | Some(MapCell::ThinWall { texture: _, orientation: _, offset_into_cell: _, ceiling_texture, floor_texture }) => {
+ let texture_coords = Vec2 {
+ // this is also the ceiling size, but we only check the floor size
+ x: ((floor_texture.width as f64 * (floor.x - cell.x as f64)) as usize & (floor_texture.width - 1)) as i32,
+ y: ((floor_texture.height as f64 * (floor.y - cell.y as f64)) as usize & (floor_texture.height - 1)) as i32,
+ };
+
+ let mut color = if is_floor {
+ (floor_texture.data[(floor_texture.width as i32 * texture_coords.y + texture_coords.x) as usize] >> 1) & 8355711
+ } else {
+ (ceiling_texture.data[(ceiling_texture.width as i32 * texture_coords.y + texture_coords.x) as usize] >> 1) & 8355711
+ };
+ if let Some(MapCell::Empty { ceiling_texture: _, floor_texture: _, fog, fog_color }) = world.at(&camera.position.as_usize()) {
+ let fog_prop = ((&floor - &camera.position).length() * fog).min(1.0);
+ if fog_prop > 0.0 {
+ let mut color_bytes = color.to_le_bytes();
+ let fog_bytes = fog_color.to_le_bytes();
+ color_bytes[0] = (fog_bytes[0] as f64 * fog_prop + color_bytes[0] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[1] = (fog_bytes[1] as f64 * fog_prop + color_bytes[1] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[2] = (fog_bytes[2] as f64 * fog_prop + color_bytes[2] as f64 * (1.0 - fog_prop)) as u8;
+ color = u32::from_le_bytes(color_bytes);
+ }
+ }
+ self.pixels[y * self.width + x] = color;
+ },
+ Some(MapCell::Wall { texture: _ }) => {},
+ None => {},
+ }
+ }
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..ff2ca7a
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,261 @@
+mod camera;
+mod framebuffer;
+mod map;
+mod texture;
+mod util;
+mod vec2;
+
+use minifb::{Window, WindowOptions};
+
+use camera::{Camera, Intersection};
+use framebuffer::Framebuffer;
+use map::{Map, MapCell};
+use texture::{Font, Texture};
+use util::{Orientation, Side, Sprite, Step};
+use vec2::Vec2;
+
+use std::rc::Rc;
+use std::time::Instant;
+
+fn main() {
+ let mut framebuffer = Framebuffer::new(600, 800);
+ let mut camera = Camera {
+ position: Vec2 { x: 3.0, y: 12.0 },
+ direction: Vec2 { x: -1.0, y: 0.0 },
+ plane: Vec2 {
+ x: 0.0,
+ y: (framebuffer.width as f64 / framebuffer.height as f64) / 2.0,
+ },
+ height: 0.0,
+ };
+
+ let mut time = Instant::now();
+ let mut old_time: Instant;
+
+ let font = Font::load_from_bmp(&include_bytes!("../res/font.bmp").to_vec(), 8);
+ let textures: Vec<Rc<Texture>> = vec![
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/eagle.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/redbrick.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/purplestone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/greystone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/bluestone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/mossy.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/wood.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/colorstone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/barrel.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/pillar.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/greenlight.bmp").to_vec(),
+ )),
+ ];
+
+ let world = Map::new(&textures);
+
+ let mut sprites = vec![
+ Sprite {
+ position: Vec2 { x: 3.0, y: 8.0 },
+ texture: textures[8].clone(),
+ scale_factor: Vec2 { x: 1.0, y: 1.0 },
+ vertical_offset: 0.0,
+ distance_from_camera: 0.0,
+ },
+ Sprite {
+ position: Vec2 { x: 3.0, y: 6.0 },
+ texture: textures[8].clone(),
+ scale_factor: Vec2 { x: 1.5, y: 1.5 },
+ vertical_offset: -150.0,
+ distance_from_camera: 0.0,
+ },
+ ];
+
+ let mut window = Window::new(
+ "Raycasting Demo",
+ framebuffer.width,
+ framebuffer.height,
+ WindowOptions::default(),
+ )
+ .unwrap();
+ window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
+
+ while window.is_open() {
+ framebuffer.draw_floor_and_ceiling(&camera, &world);
+ for x in 0..framebuffer.width {
+ let mut side_dist = Vec2::<f64>::new();
+ let mut ray = camera.get_ray(x, framebuffer.width);
+ let mut map = camera.position.as_usize();
+ let delta_dist = Vec2 {
+ x: (1.0 / ray.direction.x).abs(),
+ y: (1.0 / ray.direction.y).abs(),
+ };
+ let mut side: Side;
+ let step = Vec2 {
+ x: Step::from(ray.direction.x < 0.0),
+ y: Step::from(ray.direction.y < 0.0),
+ };
+ if ray.direction.x < 0.0 {
+ side_dist.x = (camera.position.x - (map.x as f64)) * delta_dist.x;
+ } else {
+ side_dist.x = ((map.x as f64) + 1.0 - camera.position.x) * delta_dist.x;
+ }
+ if ray.direction.y < 0.0 {
+ side_dist.y = (camera.position.y - (map.y as f64)) * delta_dist.y;
+ } else {
+ side_dist.y = ((map.y as f64) + 1.0 - camera.position.y) * delta_dist.y;
+ }
+
+ loop {
+ if side_dist.x < side_dist.y {
+ side_dist.x += delta_dist.x;
+ match step.x {
+ Step::Left => map.x -= 1,
+ Step::Right => map.x += 1,
+ }
+ side = Side::X;
+ } else {
+ side_dist.y += delta_dist.y;
+ match step.y {
+ Step::Left => map.y -= 1,
+ Step::Right => map.y += 1,
+ }
+ side = Side::Y;
+ }
+ match world.at(&map) {
+ Some(MapCell::Wall { texture }) => {
+ ray.intersections.push(Intersection {
+ side: side.clone(),
+ step: step.clone(),
+ map_coordinates: map.clone(),
+ wall_offset: Vec2 { x: 0.0, y: 0.0 },
+ });
+ if !texture.has_transparency {
+ break;
+ }
+ }
+ Some(MapCell::ThinWall {
+ texture,
+ orientation,
+ offset_into_cell,
+ ceiling_texture: _,
+ floor_texture: _,
+ }) => match orientation {
+ Orientation::XAxis => {
+ if side_dist.x - (delta_dist.x / (1.0 / offset_into_cell)) > side_dist.y
+ {
+ continue;
+ } else {
+ ray.intersections.push(Intersection {
+ side: Side::X,
+ step: step.clone(),
+ map_coordinates: map.clone(),
+ wall_offset: Vec2 {
+ x: offset_into_cell * step.x.value() as f64,
+ y: 0.0,
+ },
+ });
+ if !texture.has_transparency {
+ break;
+ }
+ }
+ }
+ Orientation::YAxis => {
+ if side_dist.y - (delta_dist.y / (1.0 / offset_into_cell)) > side_dist.x
+ {
+ continue;
+ } else {
+ ray.intersections.push(Intersection {
+ side: Side::Y,
+ step: step.clone(),
+ map_coordinates: map.clone(),
+ wall_offset: Vec2 {
+ x: 0.0,
+ y: offset_into_cell * step.y.value() as f64,
+ },
+ });
+ if !texture.has_transparency {
+ break;
+ }
+ }
+ }
+ },
+ Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) => continue,
+ None => break,
+ }
+ }
+ for intersection in &ray.intersections {
+ let perp_wall_dist = match &intersection.side {
+ Side::X => {
+ ((intersection.map_coordinates.x as f64) - camera.position.x
+ + intersection.wall_offset.x
+ + ((1 - intersection.step.x.value()) as f64) / 2.0)
+ / ray.direction.x
+ }
+ Side::Y => {
+ ((intersection.map_coordinates.y as f64) - camera.position.y
+ + intersection.wall_offset.y
+ + ((1 - intersection.step.y.value()) as f64) / 2.0)
+ / ray.direction.y
+ }
+ };
+ if let Some(cell) = world.at(&intersection.map_coordinates) {
+ framebuffer.draw_wall(
+ &camera,
+ x,
+ perp_wall_dist,
+ cell,
+ &intersection.side,
+ &ray,
+ &world,
+ );
+ }
+ }
+ }
+ framebuffer.draw_sprites(&camera, &mut sprites, &world);
+ old_time = time;
+ time = Instant::now();
+ let frame_time = (time - old_time).as_secs_f64();
+ framebuffer.write_ascii_string(
+ 0,
+ 0,
+ &format!("{:.3}", (1.0 / frame_time)).into_bytes(),
+ &font,
+ 0x00FFFFFF,
+ );
+ framebuffer.write_ascii_string(
+ 0,
+ font.glyph_size,
+ &format!("x: {:.3}, y: {:.3}", camera.position.x, camera.position.y).into_bytes(),
+ &font,
+ 0x00FFFFFF,
+ );
+ camera.update_position_with_keys(frame_time, &window, &world);
+ window
+ .update_with_buffer(&framebuffer.pixels, framebuffer.width, framebuffer.height)
+ .unwrap();
+ framebuffer.clear_z_buffer();
+ }
+}
diff --git a/src/map.rs b/src/map.rs
new file mode 100644
index 0000000..b4ac57f
--- /dev/null
+++ b/src/map.rs
@@ -0,0 +1,60 @@
+use std::rc::Rc;
+
+use crate::vec2::Vec2;
+use crate::util::Orientation;
+use crate::texture::Texture;
+
+pub enum MapCell {
+ Empty {
+ ceiling_texture: Rc<Texture>,
+ floor_texture: Rc<Texture>,
+ fog: f64,
+ fog_color: u32,
+ },
+ Wall {
+ texture: Rc<Texture>,
+ },
+ ThinWall {
+ texture: Rc<Texture>,
+ orientation: Orientation,
+ offset_into_cell: f64,
+ ceiling_texture: Rc<Texture>,
+ floor_texture: Rc<Texture>,
+ },
+}
+
+pub struct Map {
+ width: usize,
+ height: usize,
+ cells: Vec<MapCell>,
+}
+
+impl Map {
+ pub fn new(texture_atlas: &Vec<Rc<Texture>>) -> Map {
+ let layout = include_str!("../res/map.txt").replace("\n", "");
+ let mut cells = Vec::with_capacity(layout.len());
+ for cell in layout.split(',') {
+ match cell {
+ "0" => cells.push(MapCell::Empty { ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone(), fog: 0.08, fog_color: 0x00000000 }),
+ _ => cells.push(MapCell::Wall { texture: texture_atlas[cell.parse::<usize>().unwrap() - 1].clone() }),
+ }
+ }
+ cells[5 * 24 + 9] = MapCell::ThinWall { texture: texture_atlas[6].clone(), orientation: Orientation::XAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ cells[5 * 24 + 10] = MapCell::ThinWall { texture: texture_atlas[5].clone(), orientation: Orientation::XAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ cells[5 * 24 + 11] = MapCell::ThinWall { texture: texture_atlas[4].clone(), orientation: Orientation::XAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ cells[3 * 24 + 2] = MapCell::ThinWall { texture: texture_atlas[9].clone(), orientation: Orientation::YAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ Map {
+ width: 24,
+ height: 24,
+ cells,
+ }
+ }
+
+ pub fn at(&self, position: &Vec2<usize>) -> Option<&MapCell> {
+ if position.x < self.height && position.y < self.width {
+ self.cells.get(position.x * self.width + position.y)
+ } else {
+ None
+ }
+ }
+}
diff --git a/src/texture.rs b/src/texture.rs
new file mode 100644
index 0000000..2751b7a
--- /dev/null
+++ b/src/texture.rs
@@ -0,0 +1,81 @@
+pub struct Font {
+ pub charset_length: usize,
+ pub glyph_size: usize,
+ pub glyphs: Vec<bool>,
+}
+
+impl Font {
+ pub fn load_from_bmp(bmp_data: &Vec<u8>, glyph_size: usize) -> Font {
+ let data_position = u32::from_le_bytes([
+ bmp_data[0x0A],
+ bmp_data[0x0B],
+ bmp_data[0x0C],
+ bmp_data[0x0D],
+ ]);
+ let mut glyphs = Vec::new();
+ // step_by 3 assuming 24-bit depth
+ for byte_index in ((data_position as usize)..bmp_data.len()).step_by(3) {
+ glyphs.push(bmp_data[byte_index] == 0xFF);
+ }
+ Font {
+ charset_length: 256,
+ glyph_size,
+ glyphs,
+ }
+ }
+}
+
+pub struct Texture {
+ pub width: usize,
+ pub height: usize,
+ pub has_transparency: bool,
+ pub data: Vec<u32>,
+}
+
+impl Texture {
+ pub fn load_from_bmp(bmp_data: &Vec<u8>) -> Texture {
+ let data_position = u32::from_le_bytes([
+ bmp_data[0x0A],
+ bmp_data[0x0B],
+ bmp_data[0x0C],
+ bmp_data[0x0D],
+ ]);
+ // assuming windows BITMAPINFOHEADER, these are i32
+ let width = i32::from_le_bytes([
+ bmp_data[0x12],
+ bmp_data[0x13],
+ bmp_data[0x14],
+ bmp_data[0x15],
+ ]) as usize;
+ let height = i32::from_le_bytes([
+ bmp_data[0x16],
+ bmp_data[0x17],
+ bmp_data[0x18],
+ bmp_data[0x19],
+ ]) as usize;
+ let mut has_transparency = false;
+ let mut data = Vec::with_capacity(width * height);
+ // step_by 3 assuming 24-bit depth
+ for byte_index in ((data_position as usize)..bmp_data.len()).step_by(3) {
+ if bmp_data[byte_index] == 0x00
+ && bmp_data[byte_index + 1] == 0x00
+ && bmp_data[byte_index + 2] == 0x00
+ {
+ has_transparency = true;
+ }
+ data.push(u32::from_le_bytes([
+ bmp_data[byte_index],
+ bmp_data[byte_index + 1],
+ bmp_data[byte_index + 2],
+ 0x00,
+ ]));
+ }
+ data.reverse();
+ Texture {
+ width,
+ height,
+ has_transparency,
+ data,
+ }
+ }
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..dcd2b72
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,45 @@
+use std::rc::Rc;
+
+use crate::vec2::Vec2;
+use crate::texture::Texture;
+
+#[derive(Clone)]
+pub enum Side {
+ X,
+ Y,
+}
+
+pub enum Orientation {
+ XAxis,
+ YAxis,
+}
+
+#[derive(Clone)]
+pub enum Step {
+ Left,
+ Right,
+}
+
+impl Step {
+ pub fn value(&self) -> i32 {
+ match self {
+ Step::Left => -1,
+ Step::Right => 1,
+ }
+ }
+
+ pub fn from(value: bool) -> Step {
+ match value {
+ true => Step::Left,
+ false => Step::Right,
+ }
+ }
+}
+
+pub struct Sprite {
+ pub position: Vec2<f64>,
+ pub texture: Rc<Texture>,
+ pub vertical_offset: f64,
+ pub scale_factor: Vec2<f64>,
+ pub distance_from_camera: f64,
+}
diff --git a/src/vec2.rs b/src/vec2.rs
new file mode 100644
index 0000000..077433a
--- /dev/null
+++ b/src/vec2.rs
@@ -0,0 +1,52 @@
+use auto_ops::{impl_op_ex, impl_op_ex_commutative};
+
+#[derive(Clone)]
+pub struct Vec2<T> {
+ pub x: T,
+ pub y: T,
+}
+
+impl Vec2<f64> {
+ pub fn new() -> Vec2<f64> {
+ Vec2 { x: 0.0, y: 0.0 }
+ }
+
+ pub fn rotate(&mut self, radians: f64) {
+ *self = Vec2 {
+ x: self.x * radians.cos() - self.y * radians.sin(),
+ y: self.x * radians.sin() + self.y * radians.cos(),
+ };
+ }
+
+ pub fn length_squared(&self) -> f64 {
+ self.x * self.x + self.y * self.y
+ }
+
+ pub fn length(&self) -> f64 {
+ self.length_squared().sqrt()
+ }
+
+ pub fn as_usize(&self) -> Vec2<usize> {
+ Vec2 {
+ x: self.x as usize,
+ y: self.y as usize,
+ }
+ }
+}
+
+impl_op_ex!(+ |lhs: &Vec2<f64>, rhs: &Vec2<f64>| -> Vec2<f64> { Vec2 { x: lhs.x + rhs.x, y: lhs.y + rhs.y } });
+impl_op_ex!(-|lhs: &Vec2<f64>, rhs: &Vec2<f64>| -> Vec2<f64> {
+ Vec2 {
+ x: lhs.x - rhs.x,
+ y: lhs.y - rhs.y,
+ }
+});
+impl_op_ex!(+= |lhs: &mut Vec2<f64>, rhs: &Vec2<f64>| { *lhs = Vec2 { x: lhs.x + rhs.x, y: lhs.y + rhs.y } });
+impl_op_ex!(-= |lhs: &mut Vec2<f64>, rhs: &Vec2<f64>| { *lhs = Vec2 { x: lhs.x - rhs.x, y: lhs.y - rhs.y } });
+impl_op_ex_commutative!(*|lhs: &Vec2<f64>, rhs: &f64| -> Vec2<f64> {
+ Vec2 {
+ x: lhs.x * rhs,
+ y: lhs.y * rhs,
+ }
+});
+impl_op_ex!(/ |lhs: &Vec2<f64>, rhs: &f64| -> Vec2<f64> { lhs * (1.0 / rhs) });