epaint/
mesh.rs

1use crate::{Color32, TextureId, WHITE_UV, emath};
2use emath::{Pos2, Rect, Rot2, TSTransform, Vec2};
3
4/// The 2D vertex type.
5///
6/// Should be friendly to send to GPU as is.
7#[repr(C)]
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9#[cfg(any(not(feature = "unity"), feature = "_override_unity"))]
10#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
11#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
12pub struct Vertex {
13    /// Logical pixel coordinates (points).
14    /// (0,0) is the top left corner of the screen.
15    pub pos: Pos2, // 64 bit
16
17    /// Normalized texture coordinates.
18    /// (0, 0) is the top left corner of the texture.
19    /// (1, 1) is the bottom right corner of the texture.
20    pub uv: Pos2, // 64 bit
21
22    /// sRGBA with premultiplied alpha
23    pub color: Color32, // 32 bit
24}
25
26impl Vertex {
27    /// An untextured vertex
28    #[inline]
29    pub fn untextured(pos: Pos2, color: Color32) -> Self {
30        Self {
31            pos,
32            uv: WHITE_UV,
33            color,
34        }
35    }
36}
37
38#[repr(C)]
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40#[cfg(all(feature = "unity", not(feature = "_override_unity")))]
41#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
42#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
43pub struct Vertex {
44    /// Logical pixel coordinates (points).
45    /// (0,0) is the top left corner of the screen.
46    pub pos: Pos2, // 64 bit
47
48    /// sRGBA with premultiplied alpha
49    pub color: Color32, // 32 bit
50
51    /// Normalized texture coordinates.
52    /// (0, 0) is the top left corner of the texture.
53    /// (1, 1) is the bottom right corner of the texture.
54    pub uv: Pos2, // 64 bit
55}
56
57/// Textured triangles in two dimensions.
58#[derive(Clone, Debug, Default, PartialEq, Eq)]
59#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
60pub struct Mesh {
61    /// Draw as triangles (i.e. the length is always multiple of three).
62    ///
63    /// If you only support 16-bit indices you can use [`Mesh::split_to_u16`].
64    ///
65    /// egui is NOT consistent with what winding order it uses, so turn off backface culling.
66    pub indices: Vec<u32>,
67
68    /// The vertex data indexed by `indices`.
69    pub vertices: Vec<Vertex>,
70
71    /// The texture to use when drawing these triangles.
72    pub texture_id: TextureId,
73    // TODO(emilk): bounding rectangle
74}
75
76impl Mesh {
77    pub fn with_texture(texture_id: TextureId) -> Self {
78        Self {
79            texture_id,
80            ..Default::default()
81        }
82    }
83
84    /// Restore to default state, but without freeing memory.
85    pub fn clear(&mut self) {
86        self.indices.clear();
87        self.vertices.clear();
88        self.vertices = Default::default();
89    }
90
91    /// Returns the amount of memory used by the vertices and indices.
92    pub fn bytes_used(&self) -> usize {
93        std::mem::size_of::<Self>()
94            + self.vertices.len() * std::mem::size_of::<Vertex>()
95            + self.indices.len() * std::mem::size_of::<u32>()
96    }
97
98    /// Are all indices within the bounds of the contained vertices?
99    pub fn is_valid(&self) -> bool {
100        profiling::function_scope!();
101
102        if let Ok(n) = u32::try_from(self.vertices.len()) {
103            self.indices.iter().all(|&i| i < n)
104        } else {
105            false
106        }
107    }
108
109    pub fn is_empty(&self) -> bool {
110        self.indices.is_empty() && self.vertices.is_empty()
111    }
112
113    /// Iterate over the triangles of this mesh, returning vertex indices.
114    pub fn triangles(&self) -> impl Iterator<Item = [u32; 3]> + '_ {
115        self.indices
116            .chunks_exact(3)
117            .map(|chunk| [chunk[0], chunk[1], chunk[2]])
118    }
119
120    /// Calculate a bounding rectangle.
121    pub fn calc_bounds(&self) -> Rect {
122        let mut bounds = Rect::NOTHING;
123        for v in &self.vertices {
124            bounds.extend_with(v.pos);
125        }
126        bounds
127    }
128
129    /// Append all the indices and vertices of `other` to `self`.
130    ///
131    /// Panics when `other` mesh has a different texture.
132    pub fn append(&mut self, other: Self) {
133        profiling::function_scope!();
134        debug_assert!(other.is_valid(), "Other mesh is invalid");
135
136        if self.is_empty() {
137            *self = other;
138        } else {
139            self.append_ref(&other);
140        }
141    }
142
143    /// Append all the indices and vertices of `other` to `self` without
144    /// taking ownership.
145    ///
146    /// Panics when `other` mesh has a different texture.
147    pub fn append_ref(&mut self, other: &Self) {
148        debug_assert!(other.is_valid(), "Other mesh is invalid");
149
150        if self.is_empty() {
151            self.texture_id = other.texture_id;
152        } else {
153            assert_eq!(
154                self.texture_id, other.texture_id,
155                "Can't merge Mesh using different textures"
156            );
157        }
158
159        let index_offset = self.vertices.len() as u32;
160        self.indices
161            .extend(other.indices.iter().map(|index| index + index_offset));
162        self.vertices.extend(other.vertices.iter());
163    }
164
165    /// Add a colored vertex.
166    ///
167    /// Panics when the mesh has assigned a texture.
168    #[inline(always)]
169    pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
170        debug_assert!(
171            self.texture_id == TextureId::default(),
172            "Mesh has an assigned texture"
173        );
174        self.vertices.push(Vertex::untextured(pos, color));
175    }
176
177    /// Add a triangle.
178    #[inline(always)]
179    pub fn add_triangle(&mut self, a: u32, b: u32, c: u32) {
180        self.indices.extend_from_slice(&[a, b, c]);
181    }
182
183    /// Make room for this many additional triangles (will reserve 3x as many indices).
184    /// See also `reserve_vertices`.
185    #[inline(always)]
186    pub fn reserve_triangles(&mut self, additional_triangles: usize) {
187        self.indices.reserve(3 * additional_triangles);
188    }
189
190    /// Make room for this many additional vertices.
191    /// See also `reserve_triangles`.
192    #[inline(always)]
193    pub fn reserve_vertices(&mut self, additional: usize) {
194        self.vertices.reserve(additional);
195    }
196
197    /// Rectangle with a texture and color.
198    #[inline(always)]
199    pub fn add_rect_with_uv(&mut self, rect: Rect, uv: Rect, color: Color32) {
200        #![expect(clippy::identity_op)]
201        let idx = self.vertices.len() as u32;
202        self.indices
203            .extend_from_slice(&[idx + 0, idx + 1, idx + 2, idx + 2, idx + 1, idx + 3]);
204
205        self.vertices.extend_from_slice(&[
206            Vertex {
207                pos: rect.left_top(),
208                uv: uv.left_top(),
209                color,
210            },
211            Vertex {
212                pos: rect.right_top(),
213                uv: uv.right_top(),
214                color,
215            },
216            Vertex {
217                pos: rect.left_bottom(),
218                uv: uv.left_bottom(),
219                color,
220            },
221            Vertex {
222                pos: rect.right_bottom(),
223                uv: uv.right_bottom(),
224                color,
225            },
226        ]);
227    }
228
229    /// Uniformly colored rectangle.
230    #[inline(always)]
231    pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) {
232        debug_assert!(
233            self.texture_id == TextureId::default(),
234            "Mesh has an assigned texture"
235        );
236        self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color);
237    }
238
239    /// This is for platforms that only support 16-bit index buffers.
240    ///
241    /// Splits this mesh into many smaller meshes (if needed)
242    /// where the smaller meshes have 16-bit indices.
243    pub fn split_to_u16(self) -> Vec<Mesh16> {
244        debug_assert!(self.is_valid(), "Mesh is invalid");
245
246        const MAX_SIZE: u32 = u16::MAX as u32;
247
248        if self.vertices.len() <= MAX_SIZE as usize {
249            // Common-case optimization:
250            return vec![Mesh16 {
251                indices: self.indices.iter().map(|&i| i as u16).collect(),
252                vertices: self.vertices,
253                texture_id: self.texture_id,
254            }];
255        }
256
257        let mut output = vec![];
258        let mut index_cursor = 0;
259
260        while index_cursor < self.indices.len() {
261            let span_start = index_cursor;
262            let mut min_vindex = self.indices[index_cursor];
263            let mut max_vindex = self.indices[index_cursor];
264
265            while index_cursor < self.indices.len() {
266                let (mut new_min, mut new_max) = (min_vindex, max_vindex);
267                for i in 0..3 {
268                    let idx = self.indices[index_cursor + i];
269                    new_min = new_min.min(idx);
270                    new_max = new_max.max(idx);
271                }
272
273                let new_span_size = new_max - new_min + 1; // plus one, because it is an inclusive range
274                if new_span_size <= MAX_SIZE {
275                    // Triangle fits
276                    min_vindex = new_min;
277                    max_vindex = new_max;
278                    index_cursor += 3;
279                } else {
280                    break;
281                }
282            }
283
284            assert!(
285                index_cursor > span_start,
286                "One triangle spanned more than {MAX_SIZE} vertices"
287            );
288
289            let mesh = Mesh16 {
290                indices: self.indices[span_start..index_cursor]
291                    .iter()
292                    .map(|vi| {
293                        #[expect(clippy::unwrap_used)]
294                        {
295                            u16::try_from(vi - min_vindex).unwrap()
296                        }
297                    })
298                    .collect(),
299                vertices: self.vertices[(min_vindex as usize)..=(max_vindex as usize)].to_vec(),
300                texture_id: self.texture_id,
301            };
302            debug_assert!(mesh.is_valid(), "Mesh is invalid");
303            output.push(mesh);
304        }
305        output
306    }
307
308    /// Translate location by this much, in-place
309    pub fn translate(&mut self, delta: Vec2) {
310        for v in &mut self.vertices {
311            v.pos += delta;
312        }
313    }
314
315    /// Transform the mesh in-place with the given transform.
316    pub fn transform(&mut self, transform: TSTransform) {
317        for v in &mut self.vertices {
318            v.pos = transform * v.pos;
319        }
320    }
321
322    /// Rotate by some angle about an origin, in-place.
323    ///
324    /// Origin is a position in screen space.
325    pub fn rotate(&mut self, rot: Rot2, origin: Pos2) {
326        for v in &mut self.vertices {
327            v.pos = origin + rot * (v.pos - origin);
328        }
329    }
330}
331
332// ----------------------------------------------------------------------------
333
334/// A version of [`Mesh`] that uses 16-bit indices.
335///
336/// This is produced by [`Mesh::split_to_u16`] and is meant to be used for legacy render backends.
337pub struct Mesh16 {
338    /// Draw as triangles (i.e. the length is always multiple of three).
339    ///
340    /// egui is NOT consistent with what winding order it uses, so turn off backface culling.
341    pub indices: Vec<u16>,
342
343    /// The vertex data indexed by `indices`.
344    pub vertices: Vec<Vertex>,
345
346    /// The texture to use when drawing these triangles.
347    pub texture_id: TextureId,
348}
349
350impl Mesh16 {
351    /// Are all indices within the bounds of the contained vertices?
352    pub fn is_valid(&self) -> bool {
353        if let Ok(n) = u16::try_from(self.vertices.len()) {
354            self.indices.iter().all(|&i| i < n)
355        } else {
356            false
357        }
358    }
359}