spitfire_draw/
sprite.rs

1use crate::{
2    context::DrawContext,
3    utils::{Drawable, ShaderRef, TextureRef, Vertex},
4};
5use smallvec::SmallVec;
6use spitfire_glow::{
7    graphics::{Graphics, GraphicsBatch},
8    renderer::{GlowBlending, GlowTextureFiltering, GlowUniformValue},
9};
10use std::{borrow::Cow, collections::HashMap};
11use vek::{Mat4, Quaternion, Rect, Rgba, Transform, Vec2, Vec3};
12
13#[derive(Debug, Clone)]
14pub struct SpriteTexture {
15    pub sampler: Cow<'static, str>,
16    pub texture: TextureRef,
17    pub filtering: GlowTextureFiltering,
18}
19
20impl SpriteTexture {
21    pub fn new(sampler: Cow<'static, str>, texture: TextureRef) -> Self {
22        Self {
23            sampler,
24            texture,
25            filtering: GlowTextureFiltering::Linear,
26        }
27    }
28
29    pub fn filtering(mut self, value: GlowTextureFiltering) -> Self {
30        self.filtering = value;
31        self
32    }
33}
34
35#[derive(Debug, Clone)]
36pub struct Sprite {
37    pub shader: Option<ShaderRef>,
38    pub textures: SmallVec<[SpriteTexture; 4]>,
39    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
40    pub region: Rect<f32, f32>,
41    pub page: f32,
42    pub tint: Rgba<f32>,
43    pub transform: Transform<f32, f32, f32>,
44    pub size: Option<Vec2<f32>>,
45    pub pivot: Vec2<f32>,
46    pub blending: Option<GlowBlending>,
47    pub screen_space: bool,
48}
49
50impl Default for Sprite {
51    fn default() -> Self {
52        Self {
53            shader: Default::default(),
54            textures: Default::default(),
55            uniforms: Default::default(),
56            region: Rect::new(0.0, 0.0, 1.0, 1.0),
57            page: Default::default(),
58            tint: Rgba::white(),
59            transform: Default::default(),
60            size: Default::default(),
61            pivot: Default::default(),
62            blending: Default::default(),
63            screen_space: Default::default(),
64        }
65    }
66}
67
68impl Sprite {
69    pub fn single(texture: SpriteTexture) -> Self {
70        Self {
71            textures: vec![texture].into(),
72            ..Default::default()
73        }
74    }
75
76    pub fn shader(mut self, value: ShaderRef) -> Self {
77        self.shader = Some(value);
78        self
79    }
80
81    pub fn texture(mut self, value: SpriteTexture) -> Self {
82        self.textures.push(value);
83        self
84    }
85
86    pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
87        self.uniforms.insert(key, value);
88        self
89    }
90
91    pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
92        self.region = region;
93        self.page = page;
94        self
95    }
96
97    pub fn tint(mut self, value: Rgba<f32>) -> Self {
98        self.tint = value;
99        self
100    }
101
102    pub fn transform(mut self, value: Transform<f32, f32, f32>) -> Self {
103        self.transform = value;
104        self
105    }
106
107    pub fn position(mut self, value: Vec2<f32>) -> Self {
108        self.transform.position = value.into();
109        self
110    }
111
112    pub fn orientation(mut self, value: Quaternion<f32>) -> Self {
113        self.transform.orientation = value;
114        self
115    }
116
117    pub fn rotation(mut self, angle_radians: f32) -> Self {
118        self.transform.orientation = Quaternion::rotation_z(angle_radians);
119        self
120    }
121
122    pub fn scale(mut self, value: Vec2<f32>) -> Self {
123        self.transform.scale = Vec3::new(value.x, value.y, 1.0);
124        self
125    }
126
127    pub fn size(mut self, value: Vec2<f32>) -> Self {
128        self.size = Some(value);
129        self
130    }
131
132    pub fn pivot(mut self, value: Vec2<f32>) -> Self {
133        self.pivot = value;
134        self
135    }
136
137    pub fn blending(mut self, value: GlowBlending) -> Self {
138        self.blending = Some(value);
139        self
140    }
141
142    pub fn screen_space(mut self, value: bool) -> Self {
143        self.screen_space = value;
144        self
145    }
146}
147
148impl Drawable for Sprite {
149    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
150        let batch = GraphicsBatch {
151            shader: context.shader(self.shader.as_ref()),
152            uniforms: self
153                .uniforms
154                .iter()
155                .map(|(k, v)| (k.clone(), v.to_owned()))
156                .chain(std::iter::once((
157                    "u_projection_view".into(),
158                    GlowUniformValue::M4(
159                        if self.screen_space {
160                            graphics.main_camera.screen_matrix()
161                        } else {
162                            graphics.main_camera.world_matrix()
163                        }
164                        .into_col_array(),
165                    ),
166                )))
167                .chain(self.textures.iter().enumerate().map(|(index, texture)| {
168                    (texture.sampler.clone(), GlowUniformValue::I1(index as _))
169                }))
170                .collect(),
171            textures: self
172                .textures
173                .iter()
174                .filter_map(|texture| {
175                    Some((context.texture(Some(&texture.texture))?, texture.filtering))
176                })
177                .collect(),
178            blending: self.blending.unwrap_or_else(|| context.top_blending()),
179            scissor: None,
180        };
181        let transform = Mat4::from(context.top_transform()) * Mat4::from(self.transform);
182        let size = self
183            .size
184            .or_else(|| {
185                batch
186                    .textures
187                    .first()
188                    .map(|(texture, _)| Vec2::new(texture.width() as _, texture.height() as _))
189            })
190            .unwrap_or_default();
191        let offset = size * self.pivot;
192        let color = self.tint.into_array();
193        graphics.stream.batch_optimized(batch);
194        graphics.stream.transformed(
195            |stream| {
196                stream.quad([
197                    Vertex {
198                        position: [0.0, 0.0],
199                        uv: [self.region.x, self.region.y, self.page],
200                        color,
201                    },
202                    Vertex {
203                        position: [size.x, 0.0],
204                        uv: [self.region.x + self.region.w, self.region.y, self.page],
205                        color,
206                    },
207                    Vertex {
208                        position: [size.x, size.y],
209                        uv: [
210                            self.region.x + self.region.w,
211                            self.region.y + self.region.h,
212                            self.page,
213                        ],
214                        color,
215                    },
216                    Vertex {
217                        position: [0.0, size.y],
218                        uv: [self.region.x, self.region.y + self.region.h, self.page],
219                        color,
220                    },
221                ]);
222            },
223            |vertex| {
224                let point = transform.mul_point(Vec2::from(vertex.position) - offset);
225                vertex.position[0] = point.x;
226                vertex.position[1] = point.y;
227            },
228        );
229    }
230}