Skip to main content

impeller_core/
vertices.rs

1//! A triangle mesh a caller supplies directly.
2//!
3//! Everything else this canvas draws is a shape it tessellates itself, which
4//! means the triangles are always well formed and the question of what a
5//! caller might hand over does not arise. Here it does, so the checking
6//! happens once at construction rather than at every draw: a mesh that exists
7//! is a mesh that can be drawn.
8
9use crate::Color;
10use glam::{Affine2, Vec2};
11use impeller_hal::{Error, Result};
12
13/// How positions are grouped into triangles.
14///
15/// The two compact forms exist because a strip or a fan states a triangle in
16/// one vertex where a list states it in three, and a caller who has that shape
17/// already should not have to expand it. They are expanded here rather than
18/// carried to a backend, since the difference is an index buffer and both
19/// backends draw indexed triangles.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum VertexMode {
22    /// Every three positions are one triangle.
23    #[default]
24    Triangles,
25    /// Each position after the second closes a triangle with the two before
26    /// it, alternating winding so the strip is consistently wound.
27    TriangleStrip,
28    /// Each position after the second closes a triangle with the first and the
29    /// one before it.
30    TriangleFan,
31}
32
33/// A mesh of triangles in user space, with optional texture coordinates.
34///
35/// Texture coordinates run from zero to one across the image and are read per
36/// vertex, which is the point of supplying them: every other way of drawing an
37/// image here maps it by position, and a mesh that wanted that mapping would
38/// not need coordinates at all.
39#[derive(Debug, Clone, PartialEq, Default)]
40pub struct Vertices {
41    positions: Vec<Vec2>,
42    texture_coords: Vec<Vec2>,
43    colors: Vec<Color>,
44    indices: Vec<u32>,
45}
46
47impl Vertices {
48    /// A mesh from positions alone, filled by the paint.
49    ///
50    /// The paint is sampled by position, as it is for any shape: a gradient
51    /// runs across the mesh the way it would across a path covering the same
52    /// area.
53    pub fn new(mode: VertexMode, positions: Vec<Vec2>) -> Result<Self> {
54        Self::build(mode, positions, Vec::new(), Vec::new(), None)
55    }
56
57    /// A mesh whose vertices name where in the paint they read.
58    ///
59    /// Any shader reads there, which is what `dart:ui` means by a color source
60    /// at a mesh's coordinates: an image samples its texture, a gradient
61    /// measures its ramp, and a caller's program receives them as `uv` beside
62    /// the clip position and chooses. This used to say "requires an image
63    /// paint", which was true of neither the last two.
64    pub fn textured(
65        mode: VertexMode,
66        positions: Vec<Vec2>,
67        texture_coords: Vec<Vec2>,
68    ) -> Result<Self> {
69        Self::build(mode, positions, texture_coords, Vec::new(), None)
70    }
71
72    /// A mesh whose vertices each carry a color.
73    ///
74    /// The color multiplies whatever the paint produced, so a white paint
75    /// leaves the mesh's own colors, and a gradient paint is shaded by them.
76    /// Multiplying is the only combination offered: `dart:ui` takes a blend
77    /// mode here, and every other mode either discards one of the two inputs
78    /// or is not expressible without a second value per fragment.
79    pub fn colored(mode: VertexMode, positions: Vec<Vec2>, colors: Vec<Color>) -> Result<Self> {
80        Self::build(mode, positions, Vec::new(), colors, None)
81    }
82
83    /// The same, with the triangles named by index rather than by order.
84    pub fn indexed(
85        mode: VertexMode,
86        positions: Vec<Vec2>,
87        texture_coords: Vec<Vec2>,
88        indices: Vec<u32>,
89    ) -> Result<Self> {
90        Self::build(mode, positions, texture_coords, Vec::new(), Some(indices))
91    }
92
93    /// Everything at once, for a caller who has all of it.
94    pub fn full(
95        mode: VertexMode,
96        positions: Vec<Vec2>,
97        texture_coords: Vec<Vec2>,
98        colors: Vec<Color>,
99        indices: Vec<u32>,
100    ) -> Result<Self> {
101        Self::build(mode, positions, texture_coords, colors, Some(indices))
102    }
103
104    fn build(
105        mode: VertexMode,
106        positions: Vec<Vec2>,
107        texture_coords: Vec<Vec2>,
108        colors: Vec<Color>,
109        indices: Option<Vec<u32>>,
110    ) -> Result<Self> {
111        if !texture_coords.is_empty() && texture_coords.len() != positions.len() {
112            return Err(Error::Unsupported(
113                "a mesh with texture coordinates needs one per position",
114            ));
115        }
116        if !colors.is_empty() && colors.len() != positions.len() {
117            return Err(Error::Unsupported(
118                "a mesh with colors needs one per position",
119            ));
120        }
121        // Checked here rather than clamped at draw time. An index past the end
122        // reads whatever follows the buffer, which on one backend is a
123        // validation error and on the other is a triangle drawn from
124        // uninitialized floats -- so this is the difference between a refusal
125        // and a picture that differs between backends for no visible reason.
126        if let Some(indices) = &indices {
127            if let Some(bad) = indices.iter().find(|i| **i as usize >= positions.len()) {
128                let _ = bad;
129                return Err(Error::Unsupported(
130                    "a mesh index names a position the mesh does not have",
131                ));
132            }
133        }
134        if !positions.iter().all(|p| p.is_finite()) || !texture_coords.iter().all(|c| c.is_finite())
135        {
136            return Err(Error::Unsupported(
137                "a mesh position or texture coordinate is not a finite number",
138            ));
139        }
140
141        // The order the triangles are actually drawn in, which is where the
142        // mode stops mattering: a strip and a fan are two ways of writing an
143        // index buffer, and writing it here means nothing below this point has
144        // to know which was used.
145        let order: Vec<u32> = match indices {
146            Some(indices) => indices,
147            None => (0..positions.len() as u32).collect(),
148        };
149        let indices = expand(mode, &order);
150        Ok(Self {
151            positions,
152            texture_coords,
153            colors,
154            indices,
155        })
156    }
157
158    pub fn positions(&self) -> &[Vec2] {
159        &self.positions
160    }
161
162    /// Texture coordinates, or empty where the mesh carries none.
163    pub fn texture_coords(&self) -> &[Vec2] {
164        &self.texture_coords
165    }
166
167    /// Per-vertex colors, or empty where the mesh carries none.
168    pub fn colors(&self) -> &[Color] {
169        &self.colors
170    }
171
172    /// Triangle indices, three per triangle, whatever mode built them.
173    pub fn indices(&self) -> &[u32] {
174        &self.indices
175    }
176
177    pub fn is_empty(&self) -> bool {
178        self.indices.is_empty()
179    }
180}
181
182/// One piece of a sprite sheet, and where it goes.
183///
184/// The unit a batched sprite draw is built from. `source` is in texels of the
185/// sheet, because that is how a sheet's layout is known -- a packer emits
186/// pixel rectangles, and normalizing them at every call site is how one of
187/// them eventually gets normalized twice. The quad is that rectangle's size in
188/// user units before the transform, so a sprite drawn with the identity lands
189/// at its own size with its top-left corner at the origin.
190#[derive(Debug, Clone, Copy, PartialEq)]
191pub struct Sprite {
192    /// The part of the sheet to draw, in texels.
193    pub source: SourceRect,
194    /// A color multiplied into this sprite alone.
195    ///
196    /// White changes nothing, and is what [`Sprite::new`] and [`Sprite::at`]
197    /// give. This is what makes one sheet serve a whole palette -- and it is
198    /// per sprite rather than per batch, which is the difference between one
199    /// draw and one draw per color.
200    pub color: Color,
201    /// Where it goes, applied to a quad running from the origin to the
202    /// source's size.
203    ///
204    /// A full affine rather than the rotation-scale-translation that
205    /// `dart:ui` restricts this to. That restriction buys a smaller per-sprite
206    /// payload for a shader that applies the transform itself; these are
207    /// applied here, where a general transform costs exactly the same and a
208    /// caller who wants to skew a sprite is not told they may not.
209    pub transform: Affine2,
210}
211
212/// A rectangle in texels, as a sprite's source.
213///
214/// Deliberately not the canvas `Rect`, which is in user space: a sheet
215/// rectangle and a destination rectangle are different things, and the one
216/// mistake this call invites is passing one where the other belongs.
217#[derive(Debug, Clone, Copy, PartialEq)]
218pub struct SourceRect {
219    pub x: f32,
220    pub y: f32,
221    pub width: f32,
222    pub height: f32,
223}
224
225impl SourceRect {
226    pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
227        Self {
228            x,
229            y,
230            width,
231            height,
232        }
233    }
234
235    pub fn is_finite(&self) -> bool {
236        self.x.is_finite()
237            && self.y.is_finite()
238            && self.width.is_finite()
239            && self.height.is_finite()
240    }
241}
242
243impl Sprite {
244    pub fn new(source: SourceRect, transform: Affine2) -> Self {
245        Self {
246            source,
247            color: Color::WHITE,
248            transform,
249        }
250    }
251
252    /// The same sprite, tinted.
253    pub fn with_color(mut self, color: Color) -> Self {
254        self.color = color;
255        self
256    }
257
258    /// A sprite placed without rotation or scaling.
259    pub fn at(source: SourceRect, position: Vec2) -> Self {
260        Self::new(source, Affine2::from_translation(position))
261    }
262}
263
264/// Rewrite an order of vertices as a list of triangles.
265///
266/// A degenerate tail -- one or two vertices left over from a list, or fewer
267/// than three altogether -- produces no triangle rather than an error, which
268/// is what a mesh with nothing in it should do. An error would be a different
269/// claim: that the caller made a mistake, when a mesh built from a loop that
270/// found nothing is not one.
271fn expand(mode: VertexMode, order: &[u32]) -> Vec<u32> {
272    let mut out = Vec::new();
273    match mode {
274        VertexMode::Triangles => {
275            for triangle in order.chunks_exact(3) {
276                out.extend_from_slice(triangle);
277            }
278        }
279        VertexMode::TriangleStrip => {
280            for (i, window) in order.windows(3).enumerate() {
281                // Every other triangle has the opposite winding, so two of its
282                // vertices swap. Emitting them in strip order instead would
283                // alternate front and back faces, which matters the moment
284                // anything culls -- and a mesh whose triangles disagree about
285                // which side they face is wrong even where nothing does.
286                if i % 2 == 0 {
287                    out.extend_from_slice(&[window[0], window[1], window[2]]);
288                } else {
289                    out.extend_from_slice(&[window[1], window[0], window[2]]);
290                }
291            }
292        }
293        VertexMode::TriangleFan => {
294            for window in order.windows(2).skip(1) {
295                out.extend_from_slice(&[order[0], window[0], window[1]]);
296            }
297        }
298    }
299    out
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    /// The signed area of a triangle, whose sign is its winding.
307    fn winding(a: Vec2, b: Vec2, c: Vec2) -> f32 {
308        (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y)
309    }
310
311    #[test]
312    fn a_strip_comes_out_consistently_wound() {
313        // The claim the alternation exists to make, and the only one that says
314        // why it is there: emitted in strip order the triangles would alternate
315        // front and back faces, and a mesh whose triangles disagree about which
316        // way they face is wrong the moment anything culls -- and wrong in a way
317        // that draws correctly until something does.
318        //
319        // Beside the test that pins the index sequence rather than instead of
320        // it. That one builds its strip from `points`, which are collinear, so
321        // every triangle in it encloses nothing and has no winding to be
322        // consistent about: it fixes the ordering, and this fixes what the
323        // ordering is for.
324        //
325        // A zigzag rather than a straight run, because a degenerate strip has no
326        // winding to be consistent about: three collinear points enclose nothing
327        // and their signed area is zero, which agrees with everything.
328        let strip: Vec<Vec2> = (0..6)
329            .map(|i| Vec2::new(i as f32 * 10.0, if i % 2 == 0 { 0.0 } else { 12.0 }))
330            .collect();
331        let indices = expand(VertexMode::TriangleStrip, &[0, 1, 2, 3, 4, 5]);
332        assert_eq!(indices.len(), 4 * 3, "six points make four triangles");
333
334        let signs: Vec<f32> = indices
335            .chunks_exact(3)
336            .map(|t| {
337                winding(
338                    strip[t[0] as usize],
339                    strip[t[1] as usize],
340                    strip[t[2] as usize],
341                )
342                .signum()
343            })
344            .collect();
345        assert!(
346            signs.iter().all(|s| *s == signs[0]),
347            "every triangle in a strip should wind the same way, got {signs:?}"
348        );
349
350        // And the alternation is a reordering rather than a different set: each
351        // triangle still covers the three points the strip says it does.
352        for (i, t) in indices.chunks_exact(3).enumerate() {
353            let mut got = [t[0], t[1], t[2]];
354            got.sort_unstable();
355            let want = [i as u32, i as u32 + 1, i as u32 + 2];
356            assert_eq!(got, want, "triangle {i} covers the wrong points");
357        }
358    }
359
360    fn points(n: usize) -> Vec<Vec2> {
361        (0..n).map(|i| Vec2::new(i as f32, 0.0)).collect()
362    }
363
364    #[test]
365    fn a_strip_alternates_winding_so_every_triangle_faces_the_same_way() {
366        let mesh = Vertices::new(VertexMode::TriangleStrip, points(4)).expect("strip");
367        assert_eq!(mesh.indices(), &[0, 1, 2, 2, 1, 3]);
368    }
369
370    #[test]
371    fn a_fan_shares_its_first_vertex_with_every_triangle() {
372        let mesh = Vertices::new(VertexMode::TriangleFan, points(4)).expect("fan");
373        assert_eq!(mesh.indices(), &[0, 1, 2, 0, 2, 3]);
374    }
375
376    #[test]
377    fn a_list_drops_a_partial_triangle_rather_than_inventing_a_vertex() {
378        let mesh = Vertices::new(VertexMode::Triangles, points(5)).expect("list");
379        assert_eq!(mesh.indices(), &[0, 1, 2]);
380    }
381
382    #[test]
383    fn too_few_positions_for_any_triangle_is_an_empty_mesh_rather_than_an_error() {
384        for mode in [
385            VertexMode::Triangles,
386            VertexMode::TriangleStrip,
387            VertexMode::TriangleFan,
388        ] {
389            let mesh = Vertices::new(mode, points(2)).expect("two points");
390            assert!(mesh.is_empty(), "{mode:?} made a triangle from two points");
391        }
392    }
393
394    #[test]
395    fn an_index_past_the_end_is_refused_where_it_is_written() {
396        let error = Vertices::indexed(VertexMode::Triangles, points(3), Vec::new(), vec![0, 1, 3]);
397        assert!(error.is_err(), "an out-of-range index was accepted");
398    }
399
400    #[test]
401    fn texture_coordinates_have_to_match_the_positions_they_belong_to() {
402        let error = Vertices::textured(VertexMode::Triangles, points(3), points(2));
403        assert!(error.is_err(), "a short coordinate list was accepted");
404    }
405}