spitfire_draw/
nine_slice_sprite.rs

1use crate::{
2    context::DrawContext,
3    sprite::SpriteTexture,
4    utils::{Drawable, ShaderRef, Vertex, transform_to_matrix},
5};
6use smallvec::SmallVec;
7use spitfire_core::Triangle;
8use spitfire_glow::{
9    graphics::{GraphicsBatch, GraphicsTarget},
10    renderer::{GlowBlending, GlowUniformValue},
11};
12use std::{borrow::Cow, collections::HashMap};
13use vek::{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 dyn GraphicsTarget<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.state().main_camera.screen_matrix()
218                        } else {
219                            graphics.state().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            wireframe: context.wireframe,
238        };
239        let transform = context.top_transform() * transform_to_matrix(self.transform);
240        let size = self
241            .size
242            .or_else(|| {
243                batch
244                    .textures
245                    .first()
246                    .map(|(texture, _)| Vec2::new(texture.width() as _, texture.height() as _))
247            })
248            .unwrap_or_default();
249        let offset = size * self.pivot;
250        let color = self.tint.into_array();
251        let margins_source = self.margins_source.clamp();
252        let margins_target = self.margins_target.fit_to_size(size);
253        let plf = 0.0;
254        let plc = margins_target.left;
255        let prc = size.x - margins_target.right;
256        let prf = size.x;
257        let ptf = 0.0;
258        let ptc = margins_target.top;
259        let pbc = size.y - margins_target.bottom;
260        let pbf = size.y;
261        let tlf = self.region.x;
262        let tlc = self.region.x + self.region.w * margins_source.left;
263        let trc = self.region.x + (1.0 - margins_source.right) * self.region.w;
264        let trf = self.region.x + self.region.w;
265        let ttf = self.region.y;
266        let ttc = self.region.y + self.region.h * margins_source.top;
267        let tbc = self.region.y + (1.0 - margins_source.bottom) * self.region.h;
268        let tbf = self.region.y + self.region.h;
269        graphics.state_mut().stream.batch_optimized(batch);
270        graphics.state_mut().stream.transformed(
271            |stream| unsafe {
272                stream.extend_triangles(
273                    true,
274                    [
275                        Triangle { a: 0, b: 1, c: 5 },
276                        Triangle { a: 5, b: 4, c: 0 },
277                        Triangle { a: 1, b: 2, c: 6 },
278                        Triangle { a: 6, b: 5, c: 1 },
279                        Triangle { a: 2, b: 3, c: 7 },
280                        Triangle { a: 7, b: 6, c: 2 },
281                        Triangle { a: 4, b: 5, c: 9 },
282                        Triangle { a: 9, b: 8, c: 4 },
283                    ],
284                );
285                if !self.frame_only {
286                    stream.extend_triangles(
287                        true,
288                        [
289                            Triangle { a: 5, b: 6, c: 10 },
290                            Triangle { a: 10, b: 9, c: 5 },
291                        ],
292                    );
293                }
294                stream.extend_triangles(
295                    true,
296                    [
297                        Triangle { a: 6, b: 7, c: 11 },
298                        Triangle { a: 11, b: 10, c: 6 },
299                        Triangle { a: 8, b: 9, c: 13 },
300                        Triangle { a: 13, b: 12, c: 8 },
301                        Triangle { a: 9, b: 10, c: 14 },
302                        Triangle { a: 14, b: 13, c: 9 },
303                        Triangle {
304                            a: 10,
305                            b: 11,
306                            c: 15,
307                        },
308                        Triangle {
309                            a: 15,
310                            b: 14,
311                            c: 10,
312                        },
313                    ],
314                );
315                stream.extend_vertices([
316                    Vertex {
317                        position: [plf, ptf],
318                        uv: [tlf, ttf, self.page],
319                        color,
320                    },
321                    Vertex {
322                        position: [plc, ptf],
323                        uv: [tlc, ttf, self.page],
324                        color,
325                    },
326                    Vertex {
327                        position: [prc, ptf],
328                        uv: [trc, ttf, self.page],
329                        color,
330                    },
331                    Vertex {
332                        position: [prf, ptf],
333                        uv: [trf, ttf, self.page],
334                        color,
335                    },
336                    Vertex {
337                        position: [plf, ptc],
338                        uv: [tlf, ttc, self.page],
339                        color,
340                    },
341                    Vertex {
342                        position: [plc, ptc],
343                        uv: [tlc, ttc, self.page],
344                        color,
345                    },
346                    Vertex {
347                        position: [prc, ptc],
348                        uv: [trc, ttc, self.page],
349                        color,
350                    },
351                    Vertex {
352                        position: [prf, ptc],
353                        uv: [trf, ttc, self.page],
354                        color,
355                    },
356                    Vertex {
357                        position: [plf, pbc],
358                        uv: [tlf, tbc, self.page],
359                        color,
360                    },
361                    Vertex {
362                        position: [plc, pbc],
363                        uv: [tlc, tbc, self.page],
364                        color,
365                    },
366                    Vertex {
367                        position: [prc, pbc],
368                        uv: [trc, tbc, self.page],
369                        color,
370                    },
371                    Vertex {
372                        position: [prf, pbc],
373                        uv: [trf, tbc, self.page],
374                        color,
375                    },
376                    Vertex {
377                        position: [plf, pbf],
378                        uv: [tlf, tbf, self.page],
379                        color,
380                    },
381                    Vertex {
382                        position: [plc, pbf],
383                        uv: [tlc, tbf, self.page],
384                        color,
385                    },
386                    Vertex {
387                        position: [prc, pbf],
388                        uv: [trc, tbf, self.page],
389                        color,
390                    },
391                    Vertex {
392                        position: [prf, pbf],
393                        uv: [trf, tbf, self.page],
394                        color,
395                    },
396                ]);
397            },
398            |vertex| {
399                let point = transform.mul_point(Vec2::from(vertex.position) - offset);
400                vertex.position[0] = point.x;
401                vertex.position[1] = point.y;
402            },
403        );
404    }
405}