summaryrefslogtreecommitdiff
path: root/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'graphics')
-rw-r--r--graphics/Cargo.toml4
-rw-r--r--graphics/src/lib.rs4
-rw-r--r--graphics/src/render.rs294
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))
+ }
+}