Skip to main content

mirage_engine/mesh/
data.rs

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