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}