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#[repr(C)]
12#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
13pub struct Vertex {
14 pub position: Vec3,
16 pub normal: Vec3,
18 pub uv: Vec2,
20}
21
22impl Vertex {
23 pub const fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
25 Self {
26 position,
27 normal,
28 uv,
29 }
30 }
31}
32
33#[derive(Clone, Debug)]
39pub struct MeshData<P: Part = NoParts, C: Clip = NoClips> {
40 geometry: Geometry,
42 errors: Vec<MeshError>,
45 parts: PhantomData<P>,
46 clips: PhantomData<C>,
47}
48
49impl MeshData {
50 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 #[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 #[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 #[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 #[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 #[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 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 pub fn vertices(&self) -> &[Vertex] {
144 self.geometry.vertices()
145 }
146
147 pub fn indices(&self) -> &[u32] {
149 self.geometry.indices()
150 }
151
152 pub fn slots(&self) -> &[Slot] {
154 self.geometry.slots()
155 }
156
157 #[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 pub(crate) fn empty() -> Self {
172 Self::assembled(Vec::new(), Vec::new(), Vec::new())
173 }
174
175 pub(crate) fn resolved(vertices: Vec<Vertex>, indices: Vec<u32>, slots: Vec<Slot>) -> Self {
178 Self::assembled(vertices, indices, slots)
179 }
180
181 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#[doc(hidden)]
212#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
213pub enum MeshError {
214 IndexPastVertices { index: u32, vertices: usize },
216 SlotsCoverage { covered: u64, indices: usize },
218}
219
220impl MeshError {
221 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 #[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}