diff options
Diffstat (limited to 'graphics')
| -rw-r--r-- | graphics/Cargo.toml | 4 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 4 | ||||
| -rw-r--r-- | graphics/src/render.rs | 294 |
3 files changed, 184 insertions, 118 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 5bb7680..28fd32a 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -7,8 +7,8 @@ edition = "2024" dungeon = { path = "../dungeon" } raylib = "5.5" -[target.'cfg(target_os = "linux")'.dependencies] -raylib = { version = "5.5", features = ["wayland"] } +#[target.'cfg(target_os = "linux")'.dependencies] +#raylib = { version = "5.5", features = ["wayland"] } [lints] workspace = true diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 08faae3..2efcaed 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -6,7 +6,7 @@ use std::cell::RefCell; use raylib::prelude::*; use crate::assets::{AudioData, ImageData}; -use crate::render::{FrameRenderer, Renderer}; +use crate::render::{FrameRendererImpl, Renderer}; mod assets; mod render; @@ -78,7 +78,7 @@ impl Window { /// let mut window = Window::new(800, 600, "Dungeon Crawl").unwrap(); /// let mut renderer = window.renderer(); /// ``` - pub fn renderer(&mut self) -> FrameRenderer<'_> { + pub fn renderer(&mut self) -> Result<FrameRendererImpl<'_>> { self.renderer.invoke(self.handle.get_mut(), &self.thread) } diff --git a/graphics/src/render.rs b/graphics/src/render.rs index a5cdf09..7026d50 100644 --- a/graphics/src/render.rs +++ b/graphics/src/render.rs @@ -5,34 +5,80 @@ /// The (prefered) view distance of the game const VIEW_DISTANCE: i32 = 10; +/// The baseline size of all ingame sprites and tile textures +const BASE_TILE_SIZE: i32 = 16; + use std::ops::Div; -use dungeon::{Dungeon, Entity, Floor, MAP_SIZE, Pos, Tile}; +use dungeon::{Dungeon, Entity, FPos, Floor, MAP_SIZE, Pos, Tile}; use raylib::{ RaylibThread, camera::Camera2D, color::Color, - ffi, math::Vector2, - prelude::{RaylibDraw, RaylibDrawHandle, RaylibHandle, RaylibTextureModeExt}, - texture::RenderTexture2D, + prelude::{ + RaylibDraw, RaylibDrawHandle, RaylibHandle, RaylibMode2D, RaylibMode2DExt, + RaylibTextureModeExt, + }, + texture::{RaylibTexture2D, RenderTexture2D}, }; use crate::assets::ImageData; +/// The `FrameInfo` struct stores persistant information used thought a frame not +/// accessable from `RaylibDraw` +#[derive(Clone, Copy, Debug)] +struct FrameInfo { + /// The last calculated fps + fps: u32, + /// The render width of the framebuffer in pixels + width: i32, + /// The render height of the framebuffer in pixels + height: i32, + /// The tile size in pixels + tile_size: i32, +} +impl FrameInfo { + fn new(handle: &RaylibHandle) -> Self { + let fps = handle.get_fps(); + let width = handle.get_render_width(); + let height = handle.get_render_height(); + let tile_size = { + let size = width.min(height); + let dist = VIEW_DISTANCE * 2 + 1; + // TODO: force by 16 scaling levels + size.div(dist).max(BASE_TILE_SIZE) + }; + + Self { + fps, + width, + height, + tile_size, + } + } +} + /// The `Renderer` struct is the persistant renderer /// for the duration for the application. #[derive(Debug)] pub struct Renderer { /// Set of sprites to be drawn + #[expect(dead_code)] image: ImageData, /// Pre-rendered map texture that updates if the map changes /// Stores the hash of the map tiles to check this - tiles: Option<(u64, RenderTexture2D)>, + tiles_tex: Option<RenderTexture2D>, + /// Hash of tiles used to draw on `tiles_tex` + tiles_hash: u64, } impl Renderer { pub(crate) fn new(image: ImageData) -> Self { - Self { image, tiles: None } + Self { + image, + tiles_tex: None, + tiles_hash: 0, + } } /// Invokes the renderer for the current frame @@ -40,101 +86,137 @@ impl Renderer { &'a mut self, handle: &'a mut RaylibHandle, thread: &'a RaylibThread, - ) -> FrameRenderer<'a> { - let draw_handle = handle.begin_drawing(thread); + ) -> crate::Result<FrameRendererImpl<'a>> { + let info = FrameInfo::new(handle); - // calculate the scaling factor - let width = draw_handle.get_render_width(); - let height = draw_handle.get_render_height(); - let tile_size = { - let size = width.max(height); - let dist = VIEW_DISTANCE * 2 + 1; - // TODO: force by 16 scaling levels - size.div(dist).max(16) + // allocate tile texture + let size = info.tile_size; + let pixels = (MAP_SIZE as i32) * size; + match &self.tiles_tex { + Some(tex) if tex.width() == pixels => (), + _ => { + // texture is not yet allocated, or screen sized changed enough to change the tile + // size + let tex = + handle.load_render_texture(thread, pixels as u32, pixels as u32)?; + self.tiles_tex = Some(tex); + self.tiles_hash = 0; + } }; - FrameRenderer { - handle: draw_handle, + Ok(FrameRenderer { + handle: handle.begin_drawing(thread), thread, + info, renderer: self, - tile_size, - } + }) } } -/// A `FrameRenderer` is a renderer for a single -/// frame of the game. It is created per frame. -pub struct FrameRenderer<'a> { +pub type FrameRendererImpl<'a> = FrameRenderer<'a, RaylibDrawHandle<'a>>; + +pub struct FrameRenderer<'a, T> +where + T: RaylibDraw, +{ /// The current draw handle for raylib - handle: RaylibDrawHandle<'a>, + handle: T, /// The raylib thread thread: &'a RaylibThread, + /// Non drawing information for this current frame + info: FrameInfo, /// Mutable reference to the main renderer (stores persistant data) renderer: &'a mut Renderer, - /// The tile size for this frame - tile_size: i32, } -impl<'a> FrameRenderer<'a> { - /// Returns last computed fps - fn fps(&self) -> u32 { - self.handle.get_fps() +impl<'a, T> FrameRenderer<'a, T> +where + T: RaylibDraw + RaylibMode2DExt + RaylibTextureModeExt, +{ + /// Draws an entire frame + pub fn draw_frame(&mut self, dungeon: &Dungeon) { + self.clear(); + self.draw_dungeon(dungeon); + self.draw_ui(dungeon); } - /// Returns image data - #[expect(dead_code)] - fn image(&self) -> &ImageData { - &self.renderer.image + /// Draws the dungeon, (tiles and entities) + pub fn draw_dungeon(&mut self, dungeon: &Dungeon) { + let camera = dungeon.camera(); + self.update_tilemap(&dungeon.floor); + let mut renderer = self.camera_renderer(camera); + renderer.draw_tiles(); + renderer.draw_entities(dungeon); } - +} +impl<'a, T> FrameRenderer<'a, T> +where + T: RaylibDraw + RaylibMode2DExt, +{ /// Returns a raylib camera for the given position #[must_use] - fn camera(&self, dungeon: &Dungeon) -> Camera2D { - let cpos = dungeon.camera(); - let width = self.handle.get_render_width(); - let height = self.handle.get_render_height(); - Camera2D { - target: Vector2::from(cpos.xy()).scale_by(self.tile_size as f32), + fn camera_renderer<'b>( + &'b mut self, + cpos: FPos, + ) -> FrameRenderer<'b, RaylibMode2D<'b, T>> { + let width = self.info.width; + let height = self.info.height; + let camera = Camera2D { + target: Vector2::from(cpos.xy()) + .scale_by(self.info.tile_size as f32) + .max(Vector2::new(width as f32 / 2.0, height as f32 / 2.0)) + .min(Vector2::new( + (MAP_SIZE as i32 * self.info.tile_size) as f32 - (width as f32 / 2.0), + (MAP_SIZE as i32 * self.info.tile_size) as f32 - (height as f32 / 2.0), + )), offset: Vector2::new(width as f32 / 2.0, height as f32 / 2.0), rotation: 0.0, zoom: 1.0, + }; + let handle = self.handle.begin_mode2D(camera); + FrameRenderer { + handle, + thread: self.thread, + info: self.info, + renderer: self.renderer, } } +} +impl<'a, T> FrameRenderer<'a, T> +where + T: RaylibDraw + RaylibTextureModeExt, +{ + /// Draw tiles on a provided texture + fn update_tilemap(&mut self, floor: &Floor) { + let hash = floor.hash(); + if self.renderer.tiles_hash == hash { + // Texture is up to date + return; + } - /// Clear the screen - fn clear(&mut self) { - self.handle.clear_background(Color::BLACK); - } - - /// Draws an entire frame - /// - /// # Examples - /// ```no_run - /// use dungeon::Dungeon; - /// use graphics::Window; - /// let mut window = Window::new(800, 600, "Dungeon Crawl").unwrap(); - /// let mut renderer = window.renderer(); - /// let dungeon = Dungeon::new(); - /// renderer.draw_frame(&dungeon); - /// ``` - pub fn draw_frame(&mut self, dungeon: &Dungeon) -> crate::Result<()> { - self.clear(); - self.draw_dungeon(dungeon)?; - self.draw_ui(dungeon); - Ok(()) - } + let Some(tex) = self.renderer.tiles_tex.as_mut() else { + // BUG: error here? + return; + }; - /// Draws the dungeon, (tiles and entities) - pub fn draw_dungeon(&mut self, dungeon: &Dungeon) -> crate::Result<()> { - let camera = self.camera(dungeon); - unsafe { - ffi::BeginMode2D(camera.into()); + let mut handle = self.handle.begin_texture_mode(self.thread, tex); + let size = self.info.tile_size; + handle.clear_background(Color::BLACK); + for pos in Pos::values() { + let (x, y) = pos.xy(); + let color = tile_color(floor.get(pos)); + handle.draw_rectangle(x as i32 * size, y as i32 * size, size, size, color); } - self.draw_tiles(dungeon)?; - self.draw_entities(dungeon); - unsafe { - ffi::EndMode2D(); - } - Ok(()) + + self.renderer.tiles_hash = hash; + } +} +impl<'a, T> FrameRenderer<'a, T> +where + T: RaylibDraw, +{ + /// Clear the screen + pub fn clear(&mut self) { + self.handle.clear_background(Color::BLACK); } /// Draws player HP, inventory, and floor number @@ -145,14 +227,14 @@ impl<'a> FrameRenderer<'a> { } /// Draws the entities on the map - pub fn draw_entities(&mut self, dungeon: &Dungeon) { + fn draw_entities(&mut self, dungeon: &Dungeon) { self.draw_entity(&dungeon.player.entity); } /// Draws an entity #[expect(clippy::cast_possible_truncation)] - pub fn draw_entity(&mut self, entity: &Entity) { - let size = self.tile_size; + fn draw_entity(&mut self, entity: &Entity) { + let size = self.info.tile_size; let x = (entity.fpos.x() * size as f32) as i32; let y = (entity.fpos.y() * size as f32) as i32; // TODO: per entity color @@ -160,47 +242,17 @@ impl<'a> FrameRenderer<'a> { } /// Draw dungeon tiles - pub fn draw_tiles(&mut self, dungeon: &Dungeon) -> crate::Result<()> { - let hash = dungeon.floor.hash(); - let tex = match self.renderer.tiles.take() { - Some((h, tex)) if hash == h => tex, - _ => self.draw_tiles_to_tex(&dungeon.floor)?, + fn draw_tiles(&mut self) { + let Some(tex) = &self.renderer.tiles_tex else { + // BUG: error here? + return; }; - - // caculate the starting postion on the texture to draw from - self.handle.draw_texture(&tex, 0, 0, Color::WHITE); - - // save the texture - self.renderer.tiles.replace((hash, tex)); - - Ok(()) - } - - /// Draw tiles on a provided texture - fn draw_tiles_to_tex(&mut self, floor: &Floor) -> crate::Result<RenderTexture2D> { - let size = self.tile_size; - let pixels = (MAP_SIZE as i32) * size; - let mut tex = - self.handle - .load_render_texture(self.thread, pixels as u32, pixels as u32)?; - - // draw the tiles to the texture - { - let mut handle = self.handle.begin_texture_mode(self.thread, &mut tex); - handle.clear_background(Color::BLACK); - for pos in Pos::values() { - let (x, y) = pos.xy(); - let color = tile_color(floor.get(pos)); - handle.draw_rectangle(x as i32 * size, y as i32 * size, size, size, color); - } - } - - Ok(tex) + self.handle.draw_texture(tex, 0, 0, Color::WHITE); } /// Draw FPS counter - pub fn draw_fps(&mut self) { - let fps_str = format!("{}", self.fps()); + fn draw_fps(&mut self) { + let fps_str = format!("{}", self.info.fps); self.handle.draw_text(&fps_str, 10, 10, 30, Color::YELLOW); } } @@ -213,3 +265,17 @@ fn tile_color(tile: Tile) -> Color { Tile::Stairs => Color::GRAY, } } + +trait Vector2Ext { + fn min(self, other: Self) -> Self; + fn max(self, other: Self) -> Self; +} +impl Vector2Ext for Vector2 { + fn min(self, other: Self) -> Self { + Self::new(self.x.min(other.x), self.y.min(other.y)) + } + + fn max(self, other: Self) -> Self { + Self::new(self.x.max(other.x), self.y.max(other.y)) + } +} |