/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ pub mod font; pub mod misc; pub mod sprite; use font::FontTextures; use hurrycurry_protocol::glam::Vec2; use misc::MiscTextures; use sdl2::{ pixels::PixelFormatEnum, rect::{FRect, Rect}, render::{BlendMode, Canvas, Texture, TextureAccess, TextureCreator}, video::{Window, WindowContext}, }; use sprite::SpriteDraw; use std::collections::HashMap; pub struct Renderer<'a> { metadata: AtlasLayout, font_textures: FontTextures, pub misc_textures: MiscTextures, pub size: Vec2, pub ui_size: Vec2, texture: Texture<'a>, world_scale: Vec2, world_offset: Vec2, pub ui_scale: Vec2, sprites: Vec, } pub type AtlasLayout = HashMap; impl<'a> Renderer<'a> { pub fn init(texture_creator: &'a TextureCreator) -> Self { let palette = include_str!("../../assets/palette.csv") .split('\n') .filter(|l| !l.is_empty()) .map(|s| { let mut toks = s.split(","); ( toks.next().unwrap().chars().next().unwrap(), [ toks.next().unwrap().parse::().unwrap(), toks.next().unwrap().parse::().unwrap(), toks.next().unwrap().parse::().unwrap(), toks.next().unwrap().parse::().unwrap(), ], ) }) .collect::>(); let mut texels = vec![255; 1024 * 1024 * 4]; for (y, line) in include_str!("../../assets/atlas.ta").lines().enumerate() { if line.is_empty() { continue; } for (x, char) in line.chars().enumerate() { let color = palette.get(&char).unwrap(); texels[(y * 1024 + x) * 4] = color[3]; texels[(y * 1024 + x) * 4 + 1] = color[2]; texels[(y * 1024 + x) * 4 + 2] = color[1]; texels[(y * 1024 + x) * 4 + 3] = color[0]; } } let mut texture = texture_creator .create_texture( Some(PixelFormatEnum::RGBA8888), TextureAccess::Streaming, 1024, 1024, ) .unwrap(); texture.update(None, &texels, 1024 * 4).unwrap(); texture.set_blend_mode(BlendMode::Blend); let atlas_layout = include_str!("../../assets/atlas.meta.csv") .lines() .filter(|l| !l.is_empty()) .map(|l| { let mut toks = l.split(","); let x: i32 = toks.next().unwrap().parse().unwrap(); let y: i32 = toks.next().unwrap().parse().unwrap(); let w: u32 = toks.next().unwrap().parse().unwrap(); let h: u32 = toks.next().unwrap().parse().unwrap(); let name = toks.next().unwrap().to_string(); (name, Rect::new(x, y, w, h)) }) .collect::>(); Self { ui_scale: Vec2::ZERO, ui_size: Vec2::ZERO, misc_textures: MiscTextures::init(&atlas_layout), texture, font_textures: FontTextures::init(&atlas_layout), size: Vec2::ONE, metadata: atlas_layout, sprites: vec![], world_offset: Vec2::ZERO, world_scale: Vec2::ZERO, } } pub fn set_world_view(&mut self, offset: Vec2, scale: f32) { self.world_offset = offset; self.world_scale = Vec2::new(32., 24.) * scale; } pub fn set_ui_view(&mut self, scale: f32) { self.ui_scale = Vec2::splat(scale); self.ui_size = self.size / self.ui_scale; } pub fn get_world_scale(&self) -> Vec2 { self.world_scale } #[inline] pub fn atlas_layout(&self) -> &HashMap { &self.metadata } pub fn set_modulation(&mut self, r: u8, g: u8, b: u8, a: u8) { self.texture.set_alpha_mod(a); self.texture.set_color_mod(r, g, b); } pub fn reset_modulation(&mut self) { self.set_modulation(255, 255, 255, 255) } pub fn draw_world(&mut self, sprite: SpriteDraw) { self.sprites.push(SpriteDraw { tint: sprite.tint, z_order: sprite.z_order, src: sprite.src, dst: FRect::new( (sprite.dst.x + self.world_offset.x) * self.world_scale.x, (sprite.dst.y + self.world_offset.y) * self.world_scale.y, sprite.dst.w * self.world_scale.x, sprite.dst.h * self.world_scale.y, ), }) } pub fn draw_ui(&mut self, sprite: SpriteDraw) { self.sprites.push(SpriteDraw { tint: sprite.tint, z_order: sprite.z_order, src: sprite.src, dst: FRect::new( sprite.dst.x * self.ui_scale.x, sprite.dst.y * self.ui_scale.y, sprite.dst.w * self.ui_scale.x, sprite.dst.h * self.ui_scale.y, ), }) } pub fn submit(&mut self, canvas: &mut Canvas) { self.sprites.sort(); for SpriteDraw { src, dst, tint, .. } in self.sprites.drain(..) { self.texture.set_color_mod(tint[0], tint[1], tint[2]); self.texture.set_alpha_mod(tint[3]); canvas.copy_f(&self.texture, src, dst).unwrap(); } } }