Skip to main content

libdqg/renderer/
sprite.rs

1use crate::renderer::{DrawPass, Texture};
2use crate::transform::Transformable;
3use crate::types::Color;
4
5use std::sync::Arc;
6
7#[derive(Clone)]
8pub struct Sprite {
9    pub texture: Arc<Texture>,
10    pub x: f32,
11    pub y: f32,
12    pub z: f32,
13    pub width: f32,
14    pub height: f32,
15    pub src_x: f32,
16    pub src_y: f32,
17    pub src_w: f32,
18    pub src_h: f32,
19    pub tint: Color,
20    /// RGBA overlay blended over the shaded sprite (`mix(shaded, highlight.rgb, highlight.a)`),
21    /// applied on top of `tint`'s multiplicative recolor rather than replacing it. Alpha 0 (the
22    /// default) means no highlight. Intended for editor-style hover/selection feedback; only
23    /// consumed by [`Sprite::draw_world`] — screen-space [`Sprite::draw`] ignores it.
24    pub highlight: Color,
25    /// Model matrix applied to the quad in the sprite's local space, where the origin is the
26    /// quad's anchor corner and the quad spans `(0, 0)..(width, height)`. The transformed quad
27    /// is then positioned at `(x, y, z)`, so translation here is relative to that position.
28    ///
29    /// Defaults to [`glam::Mat4::IDENTITY`]. Prefer building it up through the
30    /// [`Transformable`] methods, which rotate and scale around the sprite's center.
31    pub transform: glam::Mat4,
32}
33
34impl Sprite {
35    pub fn new(texture: Arc<Texture>) -> Self {
36        Self {
37            x: 0.0,
38            y: 0.0,
39            z: 0.0,
40            width: texture.width as f32,
41            height: texture.height as f32,
42            src_x: 0.0,
43            src_y: 0.0,
44            src_w: texture.width as f32,
45            src_h: texture.height as f32,
46            texture,
47            tint: Color::new(1.0, 1.0, 1.0, 1.0),
48            highlight: Color::new(1.0, 1.0, 1.0, 0.0),
49            transform: glam::Mat4::IDENTITY,
50        }
51    }
52
53    /// Draws the sprite in screen space (pixel coordinates), ignoring the camera. Use for UI/HUD elements.
54    pub fn draw(&self, pass: &mut DrawPass) {
55        pass.draw_ui_sprite(
56            self.x, self.y, self.width, self.height,
57            self.src_x, self.src_y, self.src_w, self.src_h,
58            self.texture.width as f32, self.texture.height as f32,
59            self.tint,
60            self.transform,
61            &self.texture.bind_group,
62        );
63    }
64
65    /// Rescales `width`/`height` (preserving aspect ratio) so the sprite fits within a 1×1 unit
66    /// square in world space, without ever upscaling past its current size. [`Sprite::new`] sizes
67    /// the quad to the texture's native *pixel* dimensions, which is normally far too large as a
68    /// *world-unit* size — call this right after construction when the caller has no better size
69    /// of its own to apply (e.g. attaching a sprite with no explicit width/height).
70    pub fn fit_within_unit_square(&mut self) {
71        let longest_side = self.width.max(self.height).max(1.0);
72        self.width /= longest_side;
73        self.height /= longest_side;
74    }
75
76    /// Draws the sprite as a quad in world space, transformed by the camera.
77    pub fn draw_world(&self, pass: &mut DrawPass) {
78        pass.draw_world_sprite(
79            self.x, self.y, self.z, self.width, self.height,
80            self.src_x, self.src_y, self.src_w, self.src_h,
81            self.texture.width as f32, self.texture.height as f32,
82            self.tint,
83            self.highlight,
84            self.transform,
85            &self.texture.bind_group,
86        );
87    }
88}
89
90/// Rotations and scales pivot around the middle of the sprite's quad, so a sprite spins in
91/// place rather than swinging around its anchor corner.
92///
93/// ```no_run
94/// # use libdqg::{renderer::Sprite, Transformable};
95/// # fn demo(sprite: &mut Sprite, angle: f32) {
96/// sprite.reset_transform().rotate(angle).scale_uniform(2.0);
97/// # }
98/// ```
99impl Transformable for Sprite {
100    fn transform(&self) -> glam::Mat4 {
101        self.transform
102    }
103
104    fn transform_mut(&mut self) -> &mut glam::Mat4 {
105        &mut self.transform
106    }
107
108    fn pivot(&self) -> glam::Vec3 {
109        glam::Vec3::new(self.width * 0.5, self.height * 0.5, 0.0)
110    }
111}