diff options
Diffstat (limited to 'graphics/src/render.rs')
| -rw-r--r-- | graphics/src/render.rs | 446 |
1 files changed, 245 insertions, 201 deletions
diff --git a/graphics/src/render.rs b/graphics/src/render.rs index aaa9983..3232bf3 100644 --- a/graphics/src/render.rs +++ b/graphics/src/render.rs @@ -1,4 +1,4 @@ -use std::{io::Write, ops::Div}; +use std::{f32, io::Write}; use dungeon::{ Direction, Dungeon, Entity, EntityKind, Floor, Item, MAP_SIZE, PLAYER_INVENTORY_SIZE, @@ -48,16 +48,31 @@ macro_rules! draw_text { } /// The baseline size of all ingame sprites and tile textures -const BASE_TEXTURE_SIZE: u16 = 16; +const TEXTURE_SIZE: u16 = 16; /// The height of the wall (offset between tile layers) const WALL_HEIGHT: u16 = 7; /// The (prefered) view distance of the game -const VIEW_DISTANCE: u16 = 5; +const VIEW_DISTANCE: u16 = 6; -/// The minimum width/height we will render to -const MIN_RENDER_SIZE: u16 = BASE_TEXTURE_SIZE * (VIEW_DISTANCE + 2 + 2); +/// The size of padding in the UI +const UI_PADDING: u16 = 6; + +/// The size of the font +const FONT_SIZE: u16 = TEXTURE_SIZE; + +/// The height of the UI +const UI_HEIGHT: u16 = (UI_PADDING + FONT_SIZE) * 3 + UI_PADDING; + +/// The size of a tile drawn to the screen +const TILE_SIZE: u16 = TEXTURE_SIZE * 3; + +/// The render height of the screen +const RENDER_HEIGHT: u16 = UI_HEIGHT + (TILE_SIZE * VIEW_DISTANCE); + +/// The render width of the screen +const RENDER_WIDTH: u16 = RENDER_HEIGHT * 4 / 3; // Tile atlas.bmp textures const ATLAS_WALL_SIDE: (u16, u16) = (0, 0); @@ -78,10 +93,19 @@ const ATLAS_ERROR: (u16, u16) = (3, 3); const FULL_SOURCE_REC: Rectangle = rect! { 0.0, 0.0, - BASE_TEXTURE_SIZE, - BASE_TEXTURE_SIZE, + TEXTURE_SIZE, + TEXTURE_SIZE, }; +/* Precomputed UI text rows */ +const UI_ROW1: u16 = UI_PADDING; +const UI_ROW2: u16 = UI_ROW1 + FONT_SIZE + UI_PADDING; +const UI_ROW3: u16 = UI_ROW2 + FONT_SIZE + UI_PADDING; + +/* Precomputed UI text columns */ +const UI_COL1: u16 = UI_PADDING; +const UI_COL2: u16 = UI_COL1 + FONT_SIZE * 10; + #[derive(Debug)] struct Textures { // Tilemap @@ -127,15 +151,17 @@ pub struct Renderer { tilemap_mm: RenderTexture2D, /// Last computed hash of the tilemap (floor) tiles_hash: Option<u64>, + /// Framebuffer to render the whole (unscaled) game to + framebuffer: Option<RenderTexture2D>, /* Per Frame Caculated Data */ /// Current tile size we are rendering - tile_size: u16, + //tile_size: u16, /// Last known FPS fps: u32, /// Render width of the current frame - width: u16, + //width: u16, /// Render height of the current frame - height: u16, + //height: u16, /// The current frame frame: u64, /// Show debug UI @@ -146,11 +172,16 @@ impl Renderer { // Load resources let textures = Textures::new(handle, thread)?; // Load render textures - let tex_size = (MAP_SIZE * BASE_TEXTURE_SIZE) as u32; + let tex_size = (MAP_SIZE * TEXTURE_SIZE) as u32; let tilemap_fg = handle.load_render_texture(thread, tex_size, tex_size)?; let tilemap_bg = handle.load_render_texture(thread, tex_size, tex_size)?; let tilemap_mm = handle.load_render_texture(thread, MAP_SIZE as u32, MAP_SIZE as u32)?; + let framebuffer = handle.load_render_texture( + thread, + RENDER_WIDTH as u32, + RENDER_HEIGHT as u32, + )?; Ok(Self { textures, @@ -158,10 +189,11 @@ impl Renderer { tilemap_bg, tilemap_mm, tiles_hash: None, - tile_size: 0, + framebuffer: Some(framebuffer), + //tile_size: 0, fps: 0, - width: 0, - height: 0, + //width: 0, + //height: 0, frame: 0, debug: false, }) @@ -177,11 +209,53 @@ impl Renderer { t: &RaylibThread, dungeon: &Dungeon, ) { + let render_width = handle.get_render_width() as f32; + let render_height = handle.get_render_height() as f32; + + // Update render info before drawing self.update_per_frame_data(handle); + + // Start the frame let mut r = handle.begin_drawing(t); - r.clear_background(Color::BLACK); - self.draw_dungeon(&mut r, t, dungeon); - self.draw_ui(&mut r, dungeon); + + // Update cached tilemaps + self.update_tilemaps(&mut r, t, &dungeon.floor); + + // Draw the frame to the framebuffer + if let Some(mut fb) = self.framebuffer.take() { + // Draw the dungeon and UI + { + let mut rt = r.begin_texture_mode(t, &mut fb); + rt.clear_background(Color::BLACK); + self.draw_dungeon(&mut rt, dungeon); + self.draw_ui(&mut rt, dungeon); + } + + // Draw and scale the fb to the screen + r.clear_background(Color::BLACK); + let source_rec = rect! { + 0, 0, RENDER_WIDTH, -RENDER_HEIGHT.cast_signed(), + }; + let scale = (render_width / RENDER_WIDTH as f32) + .min(render_height / RENDER_HEIGHT as f32); + let dest_rec = rect! { + (render_width - (RENDER_WIDTH as f32 * scale))/2.0, + (render_height - (RENDER_HEIGHT as f32 * scale))/2.0, + RENDER_WIDTH as f32 * scale, + RENDER_HEIGHT as f32 * scale, + }; + r.draw_texture_pro( + &fb, + source_rec, + dest_rec, + Vector2::zero(), + 0.0, + Color::WHITE, + ); + + // Restore the fb + self.framebuffer = Some(fb); + }; } /// Update frame metadata while we still have access to the @@ -190,47 +264,26 @@ impl Renderer { fn update_per_frame_data(&mut self, handle: &RaylibHandle) { // Get last known fps self.fps = handle.get_fps(); - // Get size of framebuffer - self.width = downcast!(handle.get_render_width(), u16).max(MIN_RENDER_SIZE); - self.height = downcast!(handle.get_render_height(), u16).max(MIN_RENDER_SIZE); - // Get size (in pixels) to draw each tile - self.tile_size = { - let size = self.width.min(self.height); - let dist = VIEW_DISTANCE * 2 + 1; - let pixels = size.div(dist).max(BASE_TEXTURE_SIZE); - 1 << (u16::BITS - pixels.leading_zeros()) - }; + //// Get size of framebuffer + //self.width = downcast!(handle.get_render_width(), u16).max(MIN_RENDER_SIZE); + //self.height = downcast!(handle.get_render_height(), u16).max(MIN_RENDER_SIZE); + //// Get size (in pixels) to draw each tile + //self.tile_size = { + // let size = self.width.min(self.height); + // let dist = VIEW_DISTANCE * 2 + 1; + // let pixels = size.div(dist).max(TEXTURE_SIZE); + // 1 << (u16::BITS - pixels.leading_zeros()) + //}; // Update frame counter self.frame += 1; } - /// Returns the Raylib Camera setup with needed 2D position/transforms - fn render_camera(&self, dungeon: &Dungeon) -> Camera2D { - let cpos = dungeon.camera(); - let width = self.width; - let height = self.height; - let ui_height = self.get_ui_height(); - Camera2D { - target: Vector2::from(cpos.xy()) - .scale_by(self.tile_size.into()) - .max(vec2! {width/2, height/2}) - .min(vec2! { - (MAP_SIZE * self.tile_size) - (width/2), - (MAP_SIZE * self.tile_size) - (height/2), - }), - offset: vec2! {width/2, height/2 + ui_height/2}, - rotation: 0.0, - zoom: 1.0, - } - } - /// Draws the game dungeon - fn draw_dungeon<R>(&mut self, r: &mut R, t: &RaylibThread, dungeon: &Dungeon) + fn draw_dungeon<R>(&mut self, r: &mut R, dungeon: &Dungeon) where - R: RaylibDraw + RaylibMode2DExt + RaylibTextureModeExt, + R: RaylibDraw + RaylibMode2DExt, { - self.update_tilemaps(r, t, &dungeon.floor); - let camera = self.render_camera(dungeon); + let camera = render_camera(dungeon); let mut rc = r.begin_mode2D(camera); self.draw_bg_tilemap(&mut rc); self.draw_entities(&mut rc, dungeon); @@ -261,12 +314,11 @@ impl Renderer { where R: RaylibDraw + RaylibTextureModeExt, { - let size = BASE_TEXTURE_SIZE; let mut rt = r.begin_texture_mode(t, &mut self.tilemap_fg); rt.clear_background(Color::BLANK); for pos in Pos::values() { - let (xs, ys) = (pos.x() * size, pos.y() * size); + let (xs, ys) = (pos.x() * TEXTURE_SIZE, pos.y() * TEXTURE_SIZE); // fg layer only draws a top walls if floor.get(pos) != Tile::Wall { @@ -274,22 +326,57 @@ impl Renderer { }; // draw base wall top texture - rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_TOP, xs, ys, size, 0.0); + rt.draw_atlas( + &self.textures.atlas, + ATLAS_WALL_TOP, + xs, + ys, + TEXTURE_SIZE, + 0.0, + ); // draw top wall borders let is_wall = |dir| pos.step(dir).map_or(Tile::Wall, |p| floor.get(p)).is_wall(); if !is_wall(Direction::North) { - rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 0.0); + rt.draw_atlas( + &self.textures.atlas, + ATLAS_WALL_EDGE, + xs, + ys, + TEXTURE_SIZE, + 0.0, + ); } if !is_wall(Direction::East) { - rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 90.0); + rt.draw_atlas( + &self.textures.atlas, + ATLAS_WALL_EDGE, + xs, + ys, + TEXTURE_SIZE, + 90.0, + ); } if !is_wall(Direction::South) { - rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 180.0); + rt.draw_atlas( + &self.textures.atlas, + ATLAS_WALL_EDGE, + xs, + ys, + TEXTURE_SIZE, + 180.0, + ); } if !is_wall(Direction::West) { - rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 270.0); + rt.draw_atlas( + &self.textures.atlas, + ATLAS_WALL_EDGE, + xs, + ys, + TEXTURE_SIZE, + 270.0, + ); } } } @@ -299,7 +386,6 @@ impl Renderer { where R: RaylibDraw + RaylibTextureModeExt, { - let size = BASE_TEXTURE_SIZE; let mut rt = r.begin_texture_mode(t, &mut self.tilemap_bg); rt.clear_background(Color::BLACK); @@ -312,7 +398,14 @@ impl Renderer { Tile::Air if (x + y) % 2 == 1 => ATLAS_FLOOR_EMPTY, _ => ATLAS_ERROR, }; - rt.draw_atlas(&self.textures.atlas, idx, x * size, y * size, size, 0.0); + rt.draw_atlas( + &self.textures.atlas, + idx, + x * TEXTURE_SIZE, + y * TEXTURE_SIZE, + TEXTURE_SIZE, + 0.0, + ); } } @@ -345,7 +438,7 @@ impl Renderer { &self.tilemap_fg, 0, 0, - self.tile_size / BASE_TEXTURE_SIZE, + TILE_SIZE / TEXTURE_SIZE, WALL_HEIGHT, ); } @@ -355,13 +448,7 @@ impl Renderer { where R: RaylibDraw, { - r.draw_tilemap( - &self.tilemap_bg, - 0, - 0, - self.tile_size / BASE_TEXTURE_SIZE, - 0, - ); + r.draw_tilemap(&self.tilemap_bg, 0, 0, TILE_SIZE / TEXTURE_SIZE, 0); } /// Draws the entities on the map @@ -380,66 +467,38 @@ impl Renderer { where R: RaylibDraw, { - let size = self.tile_size as f32; + let size = TILE_SIZE as f32; let (fx, fy) = entity.fpos.xy(); let texture = match entity.kind { EntityKind::Player => &self.textures.player, _ => &self.textures.error, }; - let (x, y) = match entity.dir { + let (ax, ay) = match entity.dir { Direction::North => (0, 0), Direction::South => (0, 1), Direction::East => (1, 0), Direction::West => (1, 1), }; - let source_rec = rect! { - x * BASE_TEXTURE_SIZE, - y * BASE_TEXTURE_SIZE, - BASE_TEXTURE_SIZE, - BASE_TEXTURE_SIZE, - }; let dest_rec = rect! { fx * size - size/2.0, fy * size - size/2.0, size, size, }; - r.draw_texture_pro( - texture, - source_rec, - dest_rec, - Vector2::zero(), - 0.0, - Color::WHITE, - ); + r.draw_inv_atlas(texture, (ax, ay), dest_rec.x, dest_rec.y, size, 0.0); if self.debug { r.draw_rectangle_lines_ex(dest_rec, 1.0, Color::YELLOW); } } - /// Returns the known UI height for this frame - fn get_ui_height(&self) -> u16 { - self.tile_size - } - - /// Returns the padding used between ui elements - fn get_ui_padding(&self) -> u16 { - self.get_ui_height() / 10 - } - - /// Returns the font size for the UI - fn get_ui_font_size(&self) -> u16 { - self.get_ui_height() / 4 - } - /// Draws player HP, inventory, and floor number fn draw_ui<R>(&self, r: &mut R, dungeon: &Dungeon) where R: RaylibDraw, { // Draw UI base rect - r.draw_rectangle_ext(0, 0, self.width, self.get_ui_height(), Color::BLACK); + r.draw_rectangle_ext(0, 0, RENDER_WIDTH, UI_HEIGHT, Color::BLACK); if self.debug { self.draw_debug_ui(r, dungeon); @@ -457,32 +516,20 @@ impl Renderer { where R: RaylibDraw, { - let padding = self.get_ui_padding(); - let y = padding; + const Y: u16 = UI_PADDING; // Draw MAP vert text - let text_x = padding; - self.draw_text_vertical(r, b"MAP", text_x, y); + const TEXT_X: u16 = UI_PADDING; + self.draw_text_vertical(r, b"MAP", TEXT_X, Y); // Draw minimap in top left of UI bar - let minimap_x = text_x + self.get_ui_font_size() + padding; - let minimap_y = padding; - let size = self.get_ui_height() - padding * 2; - r.draw_tilemap(&self.tilemap_mm, minimap_x, minimap_y, size / MAP_SIZE, 0); + const MINIMAP_X: u16 = TEXT_X + FONT_SIZE + UI_PADDING; + r.draw_tilemap(&self.tilemap_mm, MINIMAP_X, Y, 1, 0); // Draw minimap entity's - let dot_size = (size / MAP_SIZE).max(1); let mut draw_dot = |pos: Pos, color| { let (x, y) = pos.xy(); - let offset_x = x * (size / MAP_SIZE); - let offset_y = y * (size / MAP_SIZE); - r.draw_rectangle_ext( - minimap_x + offset_x, - minimap_y + offset_y, - dot_size, - dot_size, - color, - ); + r.draw_pixel((MINIMAP_X + x).into(), (Y + y).into(), color); }; // Draw enemy dots @@ -500,22 +547,16 @@ impl Renderer { where R: RaylibDraw, { - let slots = downcast!(PLAYER_INVENTORY_SIZE, u16); - let padding = self.get_ui_padding(); - let font_size = self.get_ui_font_size(); - let ui_height = self.get_ui_height(); - - let text_len = font_size + padding; - let slot_len = ui_height - padding * 2; - let slots_len = slot_len * slots; - let full_len = text_len + slots_len; + const TEXT_LEN: u16 = FONT_SIZE + UI_PADDING; + const SLOT_LEN: u16 = UI_HEIGHT - UI_PADDING * 2; + const SLOTS_LEN: u16 = SLOT_LEN * PLAYER_INVENTORY_SIZE; + const FULL_LEN: u16 = TEXT_LEN + SLOTS_LEN; - let start_x = self.width / 2 - full_len / 2; - let text_x = start_x; - let slots_x = text_x + text_len; + const TEXT_X: u16 = RENDER_WIDTH / 2 - FULL_LEN / 2; + const SLOTS_X: u16 = TEXT_X + TEXT_LEN; // Draw text - self.draw_text_vertical(r, b"INV", text_x, padding); + self.draw_text_vertical(r, b"INV", TEXT_X, UI_PADDING); // Draw slots for idx in 0..PLAYER_INVENTORY_SIZE { @@ -525,27 +566,26 @@ impl Renderer { break; } - let slot_x = slots_x + slot_len * downcast!(idx, u16); - let size = ui_height - padding * 2; + let slot_x = SLOTS_X + SLOT_LEN * idx; // Draw slot container r.draw_inv_atlas( &self.textures.atlas, ATLAS_INV_CONTAINER, slot_x, - padding, - size, + UI_PADDING, + SLOT_LEN, 0.0, ); - if let Some(item) = player.inventory.get(idx) { + if let Some(item) = player.inventory.get(idx as usize) { let tex = self.textures.item_texture(item); - let item_padding = padding * 3; + const ITEM_PADDDING: u16 = UI_PADDING * 3; let dest_rec = rect! { - slot_x + item_padding / 2, - padding+ item_padding / 2, - size - item_padding, - size - item_padding, + slot_x + ITEM_PADDDING/2, + UI_PADDING + ITEM_PADDDING/2, + SLOT_LEN - ITEM_PADDDING, + SLOT_LEN - ITEM_PADDDING, }; r.draw_texture_pro( tex, @@ -567,46 +607,34 @@ impl Renderer { let health = player.entity.health.unwrap_or(0); let damage = 0; // TODO: calc damage - let padding = self.get_ui_padding(); - let font_size = self.get_ui_font_size(); - - let text_width = font_size * 3 + padding; - let icon_width = font_size + padding; - let stats_x = self.width - text_width - icon_width; - let icon_x = stats_x; - let text_x = icon_x + icon_width; + const TEXT_WIDTH: u16 = FONT_SIZE * 3 + UI_PADDING; + const ICON_WIDTH: u16 = FONT_SIZE + UI_PADDING; + const ICON_X: u16 = RENDER_WIDTH - TEXT_WIDTH - ICON_WIDTH; + const TEXT_X: u16 = RENDER_WIDTH - TEXT_WIDTH; // draw health - let heart_y = padding; - r.draw_inv_atlas( + const HEART_Y: u16 = UI_PADDING * 2; + r.draw_atlas( &self.textures.atlas, ATLAS_HEART_ICON, - icon_x, - heart_y, - icon_width, + ICON_X + UI_PADDING, + HEART_Y + UI_PADDING, + FONT_SIZE, 0.0, ); - draw_text!(self, r, text_x, heart_y + padding, b"x00", 1, "{health:02}"); + draw_text!(self, r, TEXT_X, HEART_Y, b"x00", 1, "{health:02}"); // draw damage - let damage_y = heart_y + font_size + padding; - r.draw_inv_atlas( + const DAMAGE_Y: u16 = HEART_Y + FONT_SIZE + UI_PADDING; + r.draw_atlas( &self.textures.atlas, ATLAS_DAMAGE_ICON, - icon_x, - damage_y, - icon_width, + ICON_X + UI_PADDING, + DAMAGE_Y + UI_PADDING, + FONT_SIZE, 0.0, ); - draw_text!( - self, - r, - text_x, - damage_y + padding, - b"x00", - 1, - "{damage:02}" - ); + draw_text!(self, r, TEXT_X, DAMAGE_Y, b"x00", 1, "{damage:02}"); } /// Draws debug information ontop of other UI elements @@ -614,39 +642,28 @@ impl Renderer { where R: RaylibDraw, { - let padding = self.get_ui_padding(); - let font_size = self.get_ui_font_size(); let player = &dungeon.player; - // X Positions - let col1 = padding; - let col2 = padding + font_size * 10; - - // Y Positions - let row1 = padding; - let row2 = row1 + font_size; - let row3 = row2 + font_size; - // Draw FPS - draw_text!(self, r, col1, row1, b"FPS ", 4, "{}", self.fps); + draw_text!(self, r, UI_COL1, UI_ROW1, b"FPS ", 4, "{}", self.fps); // Draw Player position let (x, y) = &player.entity.pos.xy(); - draw_text!(self, r, col1, row2, b"POS 00,00", 4, "{x:02},{y:02}"); + draw_text!(self, r, UI_COL1, UI_ROW2, b"POS 00,00", 4, "{x:02},{y:02}"); // Draw Player direction let dir = &player.entity.dir; - draw_text!(self, r, col1, row3, b"DIR ", 4, "{dir}"); + draw_text!(self, r, UI_COL1, UI_ROW3, b"DIR ", 4, "{dir}"); // Draw Player Seed let seed = &dungeon.floor.seed(); draw_text!( self, r, - col2, - row1, - b" SEED 0x0000000000000000", - 8, + UI_COL2, + UI_ROW1, + b" 0000000000000000", + 0, "{seed:X}" ); @@ -655,10 +672,10 @@ impl Renderer { draw_text!( self, r, - col2, - row2, - b" HASH 0x0000000000000000", - 8, + UI_COL2, + UI_ROW2, + b" 0000000000000000", + 0, "{hash:X}" ); @@ -667,8 +684,8 @@ impl Renderer { draw_text!( self, r, - col2, - row3, + UI_COL2, + UI_ROW3, b"FRAME ", 6, "{frame}" @@ -680,9 +697,9 @@ impl Renderer { where R: RaylibDraw, { - let spacing = self.get_ui_font_size() + self.get_ui_padding() / 2; + const SPACING: u16 = FONT_SIZE + UI_PADDING / 2; for (idx, char) in text.iter().enumerate() { - let y = y_start + downcast!(idx, u16) * spacing; + let y = y_start + downcast!(idx, u16) * SPACING; self.draw_char(r, *char, x, y); } } @@ -693,7 +710,7 @@ impl Renderer { R: RaylibDraw, { for (idx, char) in text.iter().enumerate() { - let x = x_start + downcast!(idx, u16) * self.get_ui_font_size(); + let x = x_start + downcast!(idx, u16) * FONT_SIZE; self.draw_char(r, *char, x, y); } } @@ -713,12 +730,39 @@ impl Renderer { (ax.into(), ay.into()), x, y, - self.get_ui_font_size(), + FONT_SIZE, 0.0, ); } } +/// Returns the Raylib Camera setup with needed 2D position/transforms +fn render_camera(dungeon: &Dungeon) -> Camera2D { + /// The minimum position the camera is allowed to go + const CAMERA_MIN: Vector2 = vec2! { + RENDER_WIDTH/2, RENDER_HEIGHT/2 - UI_HEIGHT/2, + }; + /// The maximum position the camera is allowed to go + const CAMERA_MAX: Vector2 = vec2! { + MAP_SIZE * TILE_SIZE - RENDER_WIDTH/2, + MAP_SIZE * TILE_SIZE - RENDER_HEIGHT/2 - UI_HEIGHT/2, + }; + + let pos = dungeon.camera(); + Camera2D { + target: Vector2::from(pos.xy()) + .scale_by(TILE_SIZE.into()) + .max(CAMERA_MIN) + .min(CAMERA_MAX), + offset: vec2! { + RENDER_WIDTH/2, + RENDER_HEIGHT/2 + UI_HEIGHT/2, + }, + rotation: 0.0, + zoom: 1.0, + } +} + trait Vector2Ext { fn min(self, other: Self) -> Self; fn max(self, other: Self) -> Self; @@ -749,10 +793,10 @@ where ) { let size_into = size.into(); let source_rec = rect! { - ax * BASE_TEXTURE_SIZE, - ay * BASE_TEXTURE_SIZE, - BASE_TEXTURE_SIZE, - BASE_TEXTURE_SIZE, + ax * TEXTURE_SIZE, + ay * TEXTURE_SIZE, + TEXTURE_SIZE, + TEXTURE_SIZE, }; let dest_rec = rect! { x.into(), |