aboutsummaryrefslogtreecommitdiff
path: root/pixel-client/src/render
diff options
context:
space:
mode:
Diffstat (limited to 'pixel-client/src/render')
-rw-r--r--pixel-client/src/render/misc.rs17
-rw-r--r--pixel-client/src/render/mod.rs158
-rw-r--r--pixel-client/src/render/sprite.rs76
3 files changed, 251 insertions, 0 deletions
diff --git a/pixel-client/src/render/misc.rs b/pixel-client/src/render/misc.rs
new file mode 100644
index 00000000..9f866568
--- /dev/null
+++ b/pixel-client/src/render/misc.rs
@@ -0,0 +1,17 @@
+use super::{sprite::Sprite, AtlasLayout};
+use hurrycurry_protocol::glam::Vec2;
+use sdl2::rect::Rect;
+
+pub struct MiscTextures {
+ pub player: Sprite,
+ pub solid: Rect,
+}
+
+impl MiscTextures {
+ pub fn init(layout: &AtlasLayout) -> Self {
+ MiscTextures {
+ player: Sprite::new(*layout.get("player+a").unwrap(), Vec2::Y * 0.3, 0.5 + 0.3),
+ solid: *layout.get("solid+a").unwrap(),
+ }
+ }
+}
diff --git a/pixel-client/src/render/mod.rs b/pixel-client/src/render/mod.rs
new file mode 100644
index 00000000..a2aea365
--- /dev/null
+++ b/pixel-client/src/render/mod.rs
@@ -0,0 +1,158 @@
+/*
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+pub mod misc;
+pub mod sprite;
+
+use hurrycurry_protocol::glam::Vec2;
+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 SpriteRenderer<'a> {
+ metadata: AtlasLayout,
+
+ pub size: Vec2,
+ texture: Texture<'a>,
+
+ view_scale: Vec2,
+ view_offset: Vec2,
+
+ sprites: Vec<SpriteDraw>,
+}
+
+pub type AtlasLayout = HashMap<String, Rect>;
+
+impl<'a> SpriteRenderer<'a> {
+ pub fn init(texture_creator: &'a TextureCreator<WindowContext>) -> 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::<u8>().unwrap(),
+ toks.next().unwrap().parse::<u8>().unwrap(),
+ toks.next().unwrap().parse::<u8>().unwrap(),
+ toks.next().unwrap().parse::<u8>().unwrap(),
+ ],
+ )
+ })
+ .collect::<HashMap<_, _>>();
+
+ 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::<HashMap<_, _>>();
+
+ Self {
+ texture,
+ size: Vec2::ONE,
+ metadata,
+ sprites: vec![],
+ view_offset: Vec2::ZERO,
+ view_scale: Vec2::ZERO,
+ }
+ }
+
+ pub fn set_view(&mut self, offset: Vec2, scale: f32) {
+ self.view_offset = offset;
+ self.view_scale = Vec2::new(32., 24.) * scale;
+ }
+ pub fn get_scale(&self) -> Vec2 {
+ self.view_scale
+ }
+
+ #[inline]
+ pub fn atlas_layout(&self) -> &HashMap<String, Rect> {
+ &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.view_offset.x) * self.view_scale.x).round(),
+ ((sprite.dst.y + self.view_offset.y) * self.view_scale.y).round(),
+ (sprite.dst.w * self.view_scale.x).round(),
+ (sprite.dst.h * self.view_scale.y).round(),
+ ),
+ })
+ }
+
+ pub fn submit(&mut self, canvas: &mut Canvas<Window>) {
+ 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();
+ }
+ }
+}
diff --git a/pixel-client/src/render/sprite.rs b/pixel-client/src/render/sprite.rs
new file mode 100644
index 00000000..711f45bf
--- /dev/null
+++ b/pixel-client/src/render/sprite.rs
@@ -0,0 +1,76 @@
+use hurrycurry_protocol::glam::Vec2;
+use sdl2::rect::{FRect, Rect};
+
+pub struct Sprite {
+ z_offset: f32,
+ src: Rect,
+ relative_dst: FRect,
+}
+
+impl Sprite {
+ pub fn new(src: Rect, anchor: Vec2, elevation: f32) -> Self {
+ let relative_dst = FRect::new(
+ anchor.x - (src.w as f32) / 32. / 2.,
+ anchor.y - (src.h as f32) / 24.,
+ (src.w as f32) / 32.,
+ (src.h as f32) / 24.,
+ );
+ Self {
+ z_offset: elevation,
+ src,
+ relative_dst,
+ }
+ }
+ pub fn new_tile(src: Rect) -> Self {
+ Self::new(src, Vec2::new(0.5, 1.0), 0.5)
+ }
+ pub fn at(&self, pos: Vec2) -> SpriteDraw {
+ SpriteDraw {
+ z_order: ((self.z_offset + pos.y) * 24.) as i32,
+ src: self.src,
+ dst: FRect::new(
+ self.relative_dst.x + pos.x,
+ self.relative_dst.y + pos.y,
+ self.relative_dst.w,
+ self.relative_dst.h,
+ ),
+ tint: [0xff; 4],
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct SpriteDraw {
+ pub tint: [u8; 4],
+ pub z_order: i32,
+ pub src: Rect,
+ pub dst: FRect,
+}
+
+impl SpriteDraw {
+ pub fn overlay(src: Rect, pos: Vec2, size: Vec2, tint: Option<[u8; 4]>) -> Self {
+ Self {
+ dst: FRect::new(pos.x, pos.y, size.x, size.y),
+ src,
+ tint: tint.unwrap_or([0xff; 4]),
+ z_order: i32::MAX,
+ }
+ }
+}
+
+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<std::cmp::Ordering> {
+ 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
+ }
+}