Skip to main content

mirage_engine/mesh/
data.rs

1use core::fmt;
2use core::marker::PhantomData;
3
4use bytemuck::{Pod, Zeroable};
5
6use crate::math::{Vec2, Vec3};
7use crate::mesh::{Animation, Clip, Geometry, NoClips, NoParts, Part, Rig, Slot};
8use crate::{Material, ReliefData, ShadingData, TextureData};
9
10/// One corner of a mesh.
11#[repr(C)]
12#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
13pub struct Vertex {
14    /// Corner position, in mesh space, in meters.
15    pub position: Vec3,
16    /// The corner's outward direction, used for lighting.
17    pub normal: Vec3,
18    /// The texture coordinate, `(0, 0)` at the texture's top left.
19    pub uv: Vec2,
20}
21
22impl Vertex {
23    /// A vertex at `position`, with `normal` and `uv`.
24    pub const fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
25        Self {
26            position,
27            normal,
28            uv,
29        }
30    }
31}
32
33/// A mesh's vertices and indices, in the slots the parts `P` select, posed
34/// by the clips `C` name.
35///
36/// The indices define counter-clockwise triangles; the draw's transform places
37/// and scales the mesh.
38#[derive(Clone, Debug)]
39pub struct MeshData<P: Part = NoParts, C: Clip = NoClips> {
40    /// Empty where the mesh has errors: such a mesh draws nothing.
41    geometry: Geometry,
42    /// The errors in how the mesh was built, which startup reports for a
43    /// cataloged value.
44    errors: Vec<MeshError>,
45    parts: PhantomData<P>,
46    clips: PhantomData<C>,
47}
48
49impl MeshData {
50    /// A mesh of one slot, drawn with [`Material::default`].
51    ///
52    /// Every index must point to a vertex; one past them is an error the
53    /// catalog run reports. Every triangle's three indices run
54    /// counter-clockwise seen from outside the mesh: a triangle seen from
55    /// behind is not drawn.
56    pub fn new(vertices: Vec<Vertex>, indices: Vec<u32>) -> Self {
57        let whole = Slot::new(indices.len() as u32, Material::default());
58        Self::assembled(vertices, indices, vec![whole])
59    }
60
61    /// Sets the material of every slot.
62    #[must_use]
63    pub fn with_material(mut self, material: Material) -> Self {
64        for slot in self.geometry.slots_mut() {
65            slot.set_material(material);
66        }
67        self
68    }
69
70    /// Samples every slot from `texture`, in place of a white default.
71    #[must_use]
72    pub fn with_texture(mut self, texture: TextureData) -> Self {
73        for slot in self.geometry.slots_mut() {
74            slot.set_texture(texture.clone());
75        }
76        self
77    }
78
79    /// Reads the relief of every slot from `relief`.
80    ///
81    /// See [`Slot::relief`] for what the channels hold and which draws read
82    /// them.
83    #[must_use]
84    pub fn with_relief(mut self, relief: ReliefData) -> Self {
85        for slot in self.geometry.slots_mut() {
86            slot.set_relief(relief.clone());
87        }
88        self
89    }
90
91    /// Reads the shading of every slot from `shading`.
92    ///
93    /// See [`Slot::shading`] for what the channels hold and what each one
94    /// scales.
95    #[must_use]
96    pub fn with_shading(mut self, shading: ShadingData) -> Self {
97        for slot in self.geometry.slots_mut() {
98            slot.set_shading(shading.clone());
99        }
100        self
101    }
102
103    /// Reads the light every slot casts per texel from `emissive`.
104    ///
105    /// See [`Slot::emissive_map`] for what scales it.
106    #[must_use]
107    pub fn with_emissive_map(mut self, emissive: TextureData) -> Self {
108        for slot in self.geometry.slots_mut() {
109            slot.set_emissive_map(emissive.clone());
110        }
111        self
112    }
113}
114
115impl<P: Part> MeshData<P, NoClips> {
116    /// A mesh of one slot per part, in the order the parts count themselves,
117    /// each slot taking the next indices.
118    ///
119    /// Required if you want a generated mesh with parts a draw repaints one
120    /// at a time. `slot` runs once per part, so every part has a slot. The
121    /// slot lengths must cover the indices exactly; otherwise that is an
122    /// error the catalog run reports. Every triangle's three indices run
123    /// counter-clockwise seen from outside the mesh: a triangle seen from
124    /// behind is not drawn.
125    pub fn in_parts(
126        vertices: Vec<Vertex>,
127        indices: Vec<u32>,
128        mut slot: impl FnMut(P) -> Slot,
129    ) -> Self {
130        let slots = P::all()
131            .into_iter()
132            .map(|part| {
133                let index = part.index();
134                slot(part).named(index)
135            })
136            .collect();
137        Self::assembled(vertices, indices, slots)
138    }
139}
140
141impl<P: Part, C: Clip> MeshData<P, C> {
142    /// The mesh's vertices, in the order the vertex buffer takes them.
143    pub fn vertices(&self) -> &[Vertex] {
144        self.geometry.vertices()
145    }
146
147    /// The triangle indices, three per triangle, counter-clockwise.
148    pub fn indices(&self) -> &[u32] {
149        self.geometry.indices()
150    }
151
152    /// The mesh's slots, in index order.
153    pub fn slots(&self) -> &[Slot] {
154        self.geometry.slots()
155    }
156
157    /// The mesh as the engine keeps it, its part type dropped, or the
158    /// errors in how it was built: the build a mesh set calls ends in this
159    /// call.
160    #[doc(hidden)]
161    pub fn erased(self) -> Result<Geometry, Vec<MeshError>> {
162        if self.errors.is_empty() {
163            Ok(self.geometry)
164        } else {
165            Err(self.errors)
166        }
167    }
168
169    /// A mesh with nothing to draw — what an asset that never resolved
170    /// becomes.
171    pub(crate) fn empty() -> Self {
172        Self::assembled(Vec::new(), Vec::new(), Vec::new())
173    }
174
175    /// A mesh whose slots already hold the index of the part naming each
176    /// of them, as a loaded source resolves them.
177    pub(crate) fn resolved(vertices: Vec<Vertex>, indices: Vec<u32>, slots: Vec<Slot>) -> Self {
178        Self::assembled(vertices, indices, slots)
179    }
180
181    /// The same mesh posed by `rig`, with one animation per clip of `C` in
182    /// clip order: what a model loads as.
183    ///
184    /// A mesh with errors in it draws nothing, so it keeps no joints either:
185    /// the rig holds one entry per vertex, and that mesh has none.
186    pub(crate) fn posed(mut self, rig: Rig, clips: Vec<Animation>) -> Self {
187        if self.errors.is_empty() {
188            self.geometry = self.geometry.posed(rig, clips);
189        }
190        self
191    }
192
193    fn assembled(vertices: Vec<Vertex>, indices: Vec<u32>, slots: Vec<Slot>) -> Self {
194        let errors = MeshError::found(&vertices, &indices, &slots);
195        let geometry = if errors.is_empty() {
196            Geometry::over(vertices, indices, slots)
197        } else {
198            Geometry::empty()
199        };
200        Self {
201            geometry,
202            errors,
203            parts: PhantomData,
204            clips: PhantomData,
205        }
206    }
207}
208
209/// One error in how a mesh was built, which startup reports under the
210/// mesh type's name.
211#[doc(hidden)]
212#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
213pub enum MeshError {
214    /// An index reaches past the vertices.
215    IndexPastVertices { index: u32, vertices: usize },
216    /// The slots cover another count of indices than the mesh holds.
217    SlotsCoverage { covered: u64, indices: usize },
218}
219
220impl MeshError {
221    /// Every error in a mesh built from these.
222    fn found(vertices: &[Vertex], indices: &[u32], slots: &[Slot]) -> Vec<Self> {
223        let past = indices
224            .iter()
225            .copied()
226            .find(|&index| (index as usize) >= vertices.len())
227            .map(|index| Self::IndexPastVertices {
228                index,
229                vertices: vertices.len(),
230            });
231        let covered: u64 = slots.iter().map(|slot| u64::from(slot.index_count())).sum();
232        let uncovered = (covered != indices.len() as u64).then_some(Self::SlotsCoverage {
233            covered,
234            indices: indices.len(),
235        });
236
237        past.into_iter().chain(uncovered).collect()
238    }
239}
240
241impl fmt::Display for MeshError {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        match self {
244            Self::IndexPastVertices { index, vertices } => {
245                write!(f, "has the index {index} past its {vertices} vertices")
246            }
247            Self::SlotsCoverage { covered, indices } => {
248                write!(
249                    f,
250                    "has slots covering {covered} indices where it holds {indices}"
251                )
252            }
253        }
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::Color;
261    use crate::math::UVec2;
262
263    fn corners(count: usize) -> Vec<Vertex> {
264        vec![Vertex::new(Vec3::ZERO, Vec3::Y, Vec2::ZERO); count]
265    }
266
267    /// Three parts named by hand, in this order.
268    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
269    enum Third {
270        First,
271        Second,
272        Last,
273    }
274
275    impl Part for Third {
276        fn from_name(_name: &str) -> Option<Self> {
277            None
278        }
279
280        fn all() -> Vec<Self> {
281            vec![Self::First, Self::Second, Self::Last]
282        }
283
284        fn index(&self) -> u32 {
285            *self as u32
286        }
287    }
288
289    #[test]
290    fn the_memory_a_mesh_holds_counts_its_corners_its_indices_and_its_pixels() {
291        let plain = MeshData::new(corners(6), (0..6).collect())
292            .erased()
293            .expect("built whole");
294        let painted = MeshData::new(corners(6), (0..6).collect())
295            .with_texture(TextureData::rgba8(UVec2::splat(2), vec![0; 16]))
296            .erased()
297            .expect("built whole");
298
299        assert_eq!(
300            plain.bytes(),
301            6 * size_of::<Vertex>() + 6 * size_of::<u32>()
302        );
303        assert_eq!(
304            painted.bytes(),
305            plain.bytes() + 16,
306            "and the texels it samples"
307        );
308    }
309
310    #[test]
311    fn a_mesh_in_parts_has_one_slot_per_part_in_the_order_they_count_themselves() {
312        let mesh = MeshData::in_parts(corners(6), (0..6).collect(), |part: Third| match part {
313            Third::First => Slot::new(3, Material::default()),
314            Third::Second => Slot::new(2, Material::color(Color::BLACK)),
315            Third::Last => Slot::new(1, Material::default()),
316        })
317        .erased()
318        .expect("built whole");
319
320        assert_eq!(mesh.part_count(), 3);
321        assert_eq!(mesh.part_indices(0), 0..3);
322        assert_eq!(mesh.part_indices(1), 3..5);
323        assert_eq!(mesh.part_indices(2), 5..6);
324        assert_eq!(
325            mesh.part_of(1),
326            Some(1),
327            "and each slot resolves to its part"
328        );
329        assert_eq!(mesh.part_material(1), Material::color(Color::BLACK));
330    }
331
332    #[test]
333    fn slots_that_do_not_cover_the_indices_exactly_are_an_error_and_draw_nothing() {
334        let short = |part: Third| {
335            Slot::new(
336                if part == Third::First { 2 } else { 0 },
337                Material::default(),
338            )
339        };
340        let mesh = MeshData::in_parts(corners(6), (0..6).collect(), short);
341
342        assert!(mesh.indices().is_empty(), "nothing of it is drawn");
343        let errors = mesh.erased().expect_err("the slots stop short");
344        assert_eq!(
345            errors,
346            vec![MeshError::SlotsCoverage {
347                covered: 2,
348                indices: 6
349            }]
350        );
351        assert_eq!(
352            errors[0].to_string(),
353            "has slots covering 2 indices where it holds 6"
354        );
355    }
356
357    #[test]
358    fn an_index_past_the_vertices_is_an_error() {
359        let mesh = MeshData::new(corners(2), vec![0, 1, 2]);
360
361        assert!(mesh.vertices().is_empty());
362        let errors = mesh.erased().expect_err("the last index reaches past");
363        assert_eq!(
364            errors,
365            vec![MeshError::IndexPastVertices {
366                index: 2,
367                vertices: 2
368            }]
369        );
370        assert_eq!(errors[0].to_string(), "has the index 2 past its 2 vertices");
371    }
372
373    #[test]
374    fn a_mesh_of_one_slot_has_no_part_to_name_it_by() {
375        let mesh = MeshData::new(corners(3), vec![0, 1, 2])
376            .with_material(Material::color(Color::BLACK))
377            .erased()
378            .expect("built whole");
379
380        assert_eq!(mesh.part_of(0), None);
381        assert_eq!(mesh.part_indices(0), 0..3);
382        assert_eq!(mesh.part_material(0), Material::color(Color::BLACK));
383    }
384
385    #[test]
386    fn the_maps_a_generated_mesh_is_built_with_are_drawn_from_its_slot() {
387        let pixels = |value| vec![value; 4];
388        let mesh = MeshData::new(corners(3), vec![0, 1, 2])
389            .with_shading(ShadingData::rgba8(UVec2::ONE, pixels(3)))
390            .with_emissive_map(TextureData::rgba8(UVec2::ONE, pixels(7)))
391            .erased()
392            .expect("built whole");
393
394        assert_eq!(
395            mesh.part_shading(0),
396            Some(&ShadingData::rgba8(UVec2::ONE, pixels(3)))
397        );
398        assert_eq!(
399            mesh.part_emissive(0),
400            Some(&TextureData::rgba8(UVec2::ONE, pixels(7)))
401        );
402    }
403
404    #[test]
405    fn a_mesh_with_nothing_in_it_draws_no_parts() {
406        let mesh = MeshData::<NoParts>::empty()
407            .erased()
408            .expect("drawing nothing is no error");
409
410        assert_eq!(mesh.part_count(), 0);
411    }
412}