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}