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#[repr(C)]
13#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
14pub struct Vertex {
15 pub position: Vec3,
17 pub normal: Vec3,
19 pub uv: Vec2,
21}
22
23impl Vertex {
24 pub const fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
26 Self {
27 position,
28 normal,
29 uv,
30 }
31 }
32}
33
34#[derive(Clone, Debug)]
40pub struct MeshData<P: Part = NoParts, C: Clip = NoClips> {
41 geometry: Geometry,
43 errors: Vec<MeshError>,
46 unresolved: Unresolved,
49 parts: PhantomData<P>,
50 clips: PhantomData<C>,
51}
52
53impl MeshData {
54 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 #[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 #[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 #[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 #[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 #[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 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 pub fn vertices(&self) -> &[Vertex] {
158 self.geometry.vertices()
159 }
160
161 pub fn indices(&self) -> &[u32] {
163 self.geometry.indices()
164 }
165
166 pub fn slots(&self) -> &[Slot] {
168 self.geometry.slots()
169 }
170
171 #[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 pub(crate) fn empty() -> Self {
190 Self::assembled(Vec::new(), Vec::new(), Vec::new())
191 }
192
193 pub(crate) fn missing(unresolved: Unresolved) -> Self {
196 Self {
197 unresolved,
198 ..Self::empty()
199 }
200 }
201
202 pub(crate) fn resolved(vertices: Vec<Vertex>, indices: Vec<u32>, slots: Vec<Slot>) -> Self {
205 Self::assembled(vertices, indices, slots)
206 }
207
208 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#[doc(hidden)]
245#[derive(Debug)]
246pub struct Built {
247 pub(crate) geometry: Geometry,
248 pub(crate) unresolved: Unresolved,
249}
250
251#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
254pub(crate) enum MeshError {
255 IndexPastVertices { index: u32, vertices: usize },
257 SlotsCoverage { covered: u64, indices: usize },
259}
260
261impl MeshError {
262 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 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 #[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}