diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/camera.rs | 96 | ||||
-rw-r--r-- | src/framebuffer.rs | 234 | ||||
-rw-r--r-- | src/main.rs | 261 | ||||
-rw-r--r-- | src/map.rs | 60 | ||||
-rw-r--r-- | src/texture.rs | 81 | ||||
-rw-r--r-- | src/util.rs | 45 | ||||
-rw-r--r-- | src/vec2.rs | 52 |
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) }); |