spitfire_draw/
nine_slice_sprite.rs

1use crate::{
2    context::DrawContext,
3    sprite::SpriteTexture,
4    utils::{Drawable, ShaderRef, Vertex},
5};
6use smallvec::SmallVec;
7use spitfire_core::Triangle;
8use spitfire_glow::{
9    graphics::{Graphics, GraphicsBatch},
10    renderer::{GlowBlending, GlowUniformValue},
11};
12use std::{borrow::Cow, collections::HashMap};
13use vek::{Mat4, Quaternion, Rect, Rgba, Transform, Vec2, Vec3};
14
15#[derive(Debug, Default, Clone, Copy)]
16pub struct NineSliceMargins {
17    pub left: f32,
18    pub right: f32,
19    pub top: f32,
20    pub bottom: f32,
21}
22
23impl NineSliceMargins {
24    pub fn clamp(self) -> Self {
25        Self {
26            left: self.left.clamp(0.0, 1.0),
27            right: self.right.clamp(0.0, 1.0),
28            top: self.top.clamp(0.0, 1.0),
29            bottom: self.bottom.clamp(0.0, 1.0),
30        }
31    }
32
33    pub fn fit_to_size(self, size: Vec2<f32>) -> Self {
34        let mut result = self;
35        let width = result.left + result.right;
36        let height = result.top + result.bottom;
37        if width > size.x {
38            result.left = result.left / width * size.x;
39            result.right = result.right / width * size.x;
40        }
41        if height > size.x {
42            result.top = result.top / height * size.y;
43            result.bottom = result.bottom / height * size.y;
44        }
45        result
46    }
47}
48
49impl From<f32> for NineSliceMargins {
50    fn from(value: f32) -> Self {
51        Self {
52            left: value,
53            right: value,
54            top: value,
55            bottom: value,
56        }
57    }
58}
59
60impl From<[f32; 2]> for NineSliceMargins {
61    fn from([hor, ver]: [f32; 2]) -> Self {
62        Self {
63            left: hor,
64            right: hor,
65            top: ver,
66            bottom: ver,
67        }
68    }
69}
70
71#[derive(Debug, Clone)]
72pub struct NineSliceSprite {
73    pub shader: Option<ShaderRef>,
74    pub textures: SmallVec<[SpriteTexture; 4]>,
75    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
76    pub region: Rect<f32, f32>,
77    pub page: f32,
78    pub margins_source: NineSliceMargins,
79    pub margins_target: NineSliceMargins,
80    pub frame_only: bool,
81    pub tint: Rgba<f32>,
82    pub transform: Transform<f32, f32, f32>,
83    pub size: Option<Vec2<f32>>,
84    pub pivot: Vec2<f32>,
85    pub blending: Option<GlowBlending>,
86    pub screen_space: bool,
87}
88
89impl Default for NineSliceSprite {
90    fn default() -> Self {
91        Self {
92            shader: Default::default(),
93            textures: Default::default(),
94            uniforms: Default::default(),
95            region: Rect::new(0.0, 0.0, 1.0, 1.0),
96            page: Default::default(),
97            margins_source: Default::default(),
98            margins_target: Default::default(),
99            frame_only: false,
100            tint: Rgba::white(),
101            transform: Default::default(),
102            size: Default::default(),
103            pivot: Default::default(),
104            blending: Default::default(),
105            screen_space: Default::default(),
106        }
107    }
108}
109
110impl NineSliceSprite {
111    pub fn single(texture: SpriteTexture) -> Self {
112        Self {
113            textures: vec![texture].into(),
114            ..Default::default()
115        }
116    }
117
118    pub fn shader(mut self, value: ShaderRef) -> Self {
119        self.shader = Some(value);
120        self
121    }
122
123    pub fn texture(mut self, value: SpriteTexture) -> Self {
124        self.textures.push(value);
125        self
126    }
127
128    pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
129        self.uniforms.insert(key, value);
130        self
131    }
132
133    pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
134        self.region = region;
135        self.page = page;
136        self
137    }
138
139    pub fn margins_source(mut self, margins: NineSliceMargins) -> Self {
140        self.margins_source = margins;
141        self
142    }
143
144    pub fn margins_target(mut self, margins: NineSliceMargins) -> Self {
145        self.margins_target = margins;
146        self
147    }
148
149    pub fn frame_only(mut self, value: bool) -> Self {
150        self.frame_only = value;
151        self
152    }
153
154    pub fn tint(mut self, value: Rgba<f32>) -> Self {
155        self.tint = value;
156        self
157    }
158
159    pub fn transform(mut self, value: Transform<f32, f32, f32>) -> Self {
160        self.transform = value;
161        self
162    }
163
164    pub fn position(mut self, value: Vec2<f32>) -> Self {
165        self.transform.position = value.into();
166        self
167    }
168
169    pub fn orientation(mut self, value: Quaternion<f32>) -> Self {
170        self.transform.orientation = value;
171        self
172    }
173
174    pub fn rotation(mut self, angle_radians: f32) -> Self {
175        self.transform.orientation = Quaternion::rotation_z(angle_radians);
176        self
177    }
178
179    pub fn scale(mut self, value: Vec2<f32>) -> Self {
180        self.transform.scale = Vec3::new(value.x, value.y, 1.0);
181        self
182    }
183
184    pub fn size(mut self, value: Vec2<f32>) -> Self {
185        self.size = Some(value);
186        self
187    }
188
189    pub fn pivot(mut self, value: Vec2<f32>) -> Self {
190        self.pivot = value;
191        self
192    }
193
194    pub fn blending(mut self, value: GlowBlending) -> Self {
195        self.blending = Some(value);
196        self
197    }
198
199    pub fn screen_space(mut self, value: bool) -> Self {
200        self.screen_space = value;
201        self
202    }
203}
204
205impl Drawable for NineSliceSprite {
206    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
207        let batch = GraphicsBatch {
208            shader: context.shader(self.shader.as_ref()),
209            uniforms: self
210                .uniforms
211                .iter()
212                .map(|(k, v)| (k.clone(), v.to_owned()))
213                .chain(std::iter::once((
214                    "u_projection_view".into(),
215                    GlowUniformValue::M4(
216                        if self.screen_space {
217                            graphics.main_camera.screen_matrix()
218                        } else {
219                            graphics.main_camera.world_matrix()
220                        }
221                        .into_col_array(),
222                    ),
223                )))
224                .chain(self.textures.iter().enumerate().map(|(index, texture)| {
225                    (texture.sampler.clone(), GlowUniformValue::I1(index as _))
226                }))
227                .collect(),
228            textures: self
229                .textures
230                .iter()
231                .filter_map(|texture| {
232                    Some((context.texture(Some(&texture.texture))?, texture.filtering))
233                })
234                .collect(),
235            blending: self.blending.unwrap_or_else(|| context.top_blending()),
236            scissor: None,
237        };
238        let transform = Mat4::from(context.top_transform()) * Mat4::from(self.transform);
239        let size = self
240            .size
241            .or_else(|| {
242                batch
243                    .textures
244                    .first()
245                    .map(|(texture, _)| Vec2::new(texture.width() as _, texture.height() as _))
246            })
247            .unwrap_or_default();
248        let offset = size * self.pivot;
249        let color = self.tint.into_array();
250        let margins_source = self.margins_source.clamp();
251        let margins_target = self.margins_target.fit_to_size(size);
252        let plf = 0.0;
253        let plc = margins_target.left;
254        let prc = size.x - margins_target.right;
255        let prf = size.x;
256        let ptf = 0.0;
257        let ptc = margins_target.top;
258        let pbc = size.y - margins_target.bottom;
259        let pbf = size.y;
260        let tlf = self.region.x;
261        let tlc = self.region.x + self.region.w * margins_source.left;
262        let trc = self.region.x + (1.0 - margins_source.right) * self.region.w;
263        let trf = self.region.x + self.region.w;
264        let ttf = self.region.y;
265        let ttc = self.region.y + self.region.h * margins_source.top;
266        let tbc = self.region.y + (1.0 - margins_source.bottom) * self.region.h;
267        let tbf = self.region.y + self.region.h;
268        graphics.stream.batch_optimized(batch);
269        graphics.stream.transformed(
270            |stream| unsafe {
271                stream.extend_triangles(
272                    true,
273                    [
274                        Triangle { a: 0, b: 1, c: 5 },
275                        Triangle { a: 5, b: 4, c: 0 },
276                        Triangle { a: 1, b: 2, c: 6 },
277                        Triangle { a: 6, b: 5, c: 1 },
278                        Triangle { a: 2, b: 3, c: 7 },
279                        Triangle { a: 7, b: 6, c: 2 },
280                        Triangle { a: 4, b: 5, c: 9 },
281                        Triangle { a: 9, b: 8, c: 4 },
282                    ],
283                );
284                if !self.frame_only {
285                    stream.extend_triangles(
286                        true,
287                        [
288                            Triangle { a: 5, b: 6, c: 10 },
289                            Triangle { a: 10, b: 9, c: 5 },
290                        ],
291                    );
292                }
293                stream.extend_triangles(
294                    true,
295                    [
296                        Triangle { a: 6, b: 7, c: 11 },
297                        Triangle { a: 11, b: 10, c: 6 },
298                        Triangle { a: 8, b: 9, c: 13 },
299                        Triangle { a: 13, b: 12, c: 8 },
300                        Triangle { a: 9, b: 10, c: 14 },
301                        Triangle { a: 14, b: 13, c: 9 },
302                        Triangle {
303                            a: 10,
304                            b: 11,
305                            c: 15,
306                        },
307                        Triangle {
308                            a: 15,
309                            b: 14,
310                            c: 10,
311                        },
312                    ],
313                );
314                stream.extend_vertices([
315                    Vertex {
316                        position: [plf, ptf],
317                        uv: [tlf, ttf, self.page],
318                        color,
319                    },
320                    Vertex {
321                        position: [plc, ptf],
322                        uv: [tlc, ttf, self.page],
323                        color,
324                    },
325                    Vertex {
326                        position: [prc, ptf],
327                        uv: [trc, ttf, self.page],
328                        color,
329                    },
330                    Vertex {
331                        position: [prf, ptf],
332                        uv: [trf, ttf, self.page],
333                        color,
334                    },
335                    Vertex {
336                        position: [plf, ptc],
337                        uv: [tlf, ttc, self.page],
338                        color,
339                    },
340                    Vertex {
341                        position: [plc, ptc],
342                        uv: [tlc, ttc, self.page],
343                        color,
344                    },
345                    Vertex {
346                        position: [prc, ptc],
347                        uv: [trc, ttc, self.page],
348                        color,
349                    },
350                    Vertex {
351                        position: [prf, ptc],
352                        uv: [trf, ttc, self.page],
353                        color,
354                    },
355                    Vertex {
356                        position: [plf, pbc],
357                        uv: [tlf, tbc, self.page],
358                        color,
359                    },
360                    Vertex {
361                        position: [plc, pbc],
362                        uv: [tlc, tbc, self.page],
363                        color,
364                    },
365                    Vertex {
366                        position: [prc, pbc],
367                        uv: [trc, tbc, self.page],
368                        color,
369                    },
370                    Vertex {
371                        position: [prf, pbc],
372                        uv: [trf, tbc, self.page],
373                        color,
374                    },
375                    Vertex {
376                        position: [plf, pbf],
377                        uv: [tlf, tbf, self.page],
378                        color,
379                    },
380                    Vertex {
381                        position: [plc, pbf],
382                        uv: [tlc, tbf, self.page],
383                        color,
384                    },
385                    Vertex {
386                        position: [prc, pbf],
387                        uv: [trc, tbf, self.page],
388                        color,
389                    },
390                    Vertex {
391                        position: [prf, pbf],
392                        uv: [trf, tbf, self.page],
393                        color,
394                    },
395                ]);
396            },
397            |vertex| {
398                let point = transform.mul_point(Vec2::from(vertex.position) - offset);
399                vertex.position[0] = point.x;
400                vertex.position[1] = point.y;
401            },
402        );
403    }
404}