/* 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 . */ use hurrycurry_protocol::glam::Vec2; use sdl2::{ pixels::PixelFormatEnum, rect::{FRect, Rect}, render::{BlendMode, Canvas, Texture, TextureAccess, TextureCreator}, video::{Window, WindowContext}, }; use std::collections::HashMap; pub struct SpriteRenderer<'a> { metadata: HashMap, misc_textures: MiscTextures, texture: Texture<'a>, view_scale: Vec2, view_offset: Vec2, sprites: Vec, } pub struct MiscTextures { pub player: Rect, } pub struct SpriteDraw { z_order: i32, src: Rect, dst: FRect, } pub struct SpriteRect { z_offset: f32, src: Rect, relative_dst: FRect, } impl<'a> SpriteRenderer<'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 + 0] = 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 metadata = 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 { misc_textures: MiscTextures { player: *metadata.get("player+a").unwrap(), }, texture, metadata, sprites: vec![], view_offset: Vec2::ZERO, view_scale: Vec2::splat(3.), } } #[inline] pub fn metadata(&self) -> &HashMap { &self.metadata } #[inline] pub fn misc_textures(&self) -> &MiscTextures { &self.misc_textures } pub fn draw(&mut self, z_order: f32, src: Rect, dst: FRect) { self.sprites.push(SpriteDraw { z_order: (z_order * 24.) as i32, src, dst: FRect::new( ((dst.x + self.view_offset.x) * 32.).round() * self.view_scale.x, ((dst.y + self.view_offset.y) * 24.).round() * self.view_scale.y, (dst.w * 32.).round() * self.view_scale.x, (dst.h * 24.).round() * self.view_scale.y, ), }) } pub fn submit(&mut self, canvas: &mut Canvas) { self.sprites.sort(); for SpriteDraw { src, dst, .. } in self.sprites.drain(..) { canvas.copy_f(&self.texture, src, dst).unwrap(); } } } impl Ord for SpriteDraw { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.z_order.cmp(&other.z_order) } } impl PartialOrd for SpriteDraw { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(&other)) } } impl Eq for SpriteDraw {} impl PartialEq for SpriteDraw { fn eq(&self, other: &Self) -> bool { self.z_order == other.z_order && self.src == other.src && self.dst == other.dst } } impl SpriteRect { pub fn draw_at(&self, ctx: &mut SpriteRenderer, pos: Vec2) { ctx.draw( self.z_offset + pos.y + 5., self.src, FRect::new( self.relative_dst.x + pos.x, self.relative_dst.y + pos.y, self.relative_dst.w, self.relative_dst.h, ), ) } pub fn new(src: Rect) -> Self { let relative_dst = FRect::new( 0.0 - src.width() as f32 / 32. / 2., 0.3 - src.height() as f32 / 24., src.width() as f32 / 32., src.height() as f32 / 24., ); Self { z_offset: -relative_dst.h, relative_dst, src, } } }