/*
Hurry Curry! - a game about cooking
Copyright (C) 2025 Hurry Curry! Contributors
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();
}
}
pub fn num_sprites(&self) -> usize {
self.sprites.len()
}
}