use core::fmt;
use core::marker::PhantomData;
use bytemuck::{Pod, Zeroable};
use crate::math::{Vec2, Vec3};
use crate::mesh::{Animation, Clip, Geometry, NoClips, NoParts, Part, Rig, Slot};
use crate::{Material, ReliefData, ShadingData, TextureData};
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Vertex {
pub position: Vec3,
pub normal: Vec3,
pub uv: Vec2,
}
impl Vertex {
pub const fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
Self {
position,
normal,
uv,
}
}
}
#[derive(Clone, Debug)]
pub struct MeshData<P: Part = NoParts, C: Clip = NoClips> {
geometry: Geometry,
errors: Vec<MeshError>,
parts: PhantomData<P>,
clips: PhantomData<C>,
}
impl MeshData {
pub fn new(vertices: Vec<Vertex>, indices: Vec<u32>) -> Self {
let whole = Slot::new(indices.len() as u32, Material::default());
Self::assembled(vertices, indices, vec![whole])
}
#[must_use]
pub fn with_material(mut self, material: Material) -> Self {
for slot in self.geometry.slots_mut() {
slot.set_material(material);
}
self
}
#[must_use]
pub fn with_texture(mut self, texture: TextureData) -> Self {
for slot in self.geometry.slots_mut() {
slot.set_texture(texture.clone());
}
self
}
#[must_use]
pub fn with_relief(mut self, relief: ReliefData) -> Self {
for slot in self.geometry.slots_mut() {
slot.set_relief(relief.clone());
}
self
}
#[must_use]
pub fn with_shading(mut self, shading: ShadingData) -> Self {
for slot in self.geometry.slots_mut() {
slot.set_shading(shading.clone());
}
self
}
#[must_use]
pub fn with_emissive_map(mut self, emissive: TextureData) -> Self {
for slot in self.geometry.slots_mut() {
slot.set_emissive_map(emissive.clone());
}
self
}
}
impl<P: Part> MeshData<P, NoClips> {
pub fn in_parts(
vertices: Vec<Vertex>,
indices: Vec<u32>,
mut slot: impl FnMut(P) -> Slot,
) -> Self {
let slots = P::all()
.into_iter()
.map(|part| {
let index = part.index();
slot(part).named(index)
})
.collect();
Self::assembled(vertices, indices, slots)
}
}
impl<P: Part, C: Clip> MeshData<P, C> {
pub fn vertices(&self) -> &[Vertex] {
self.geometry.vertices()
}
pub fn indices(&self) -> &[u32] {
self.geometry.indices()
}
pub fn slots(&self) -> &[Slot] {
self.geometry.slots()
}
#[doc(hidden)]
pub fn erased(self) -> Result<Geometry, Vec<MeshError>> {
if self.errors.is_empty() {
Ok(self.geometry)
} else {
Err(self.errors)
}
}
pub(crate) fn empty() -> Self {
Self::assembled(Vec::new(), Vec::new(), Vec::new())
}
pub(crate) fn resolved(vertices: Vec<Vertex>, indices: Vec<u32>, slots: Vec<Slot>) -> Self {
Self::assembled(vertices, indices, slots)
}
pub(crate) fn posed(mut self, rig: Rig, clips: Vec<Animation>) -> Self {
if self.errors.is_empty() {
self.geometry = self.geometry.posed(rig, clips);
}
self
}
fn assembled(vertices: Vec<Vertex>, indices: Vec<u32>, slots: Vec<Slot>) -> Self {
let errors = MeshError::found(&vertices, &indices, &slots);
let geometry = if errors.is_empty() {
Geometry::over(vertices, indices, slots)
} else {
Geometry::empty()
};
Self {
geometry,
errors,
parts: PhantomData,
clips: PhantomData,
}
}
}
#[doc(hidden)]
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum MeshError {
IndexPastVertices { index: u32, vertices: usize },
SlotsCoverage { covered: u64, indices: usize },
}
impl MeshError {
fn found(vertices: &[Vertex], indices: &[u32], slots: &[Slot]) -> Vec<Self> {
let past = indices
.iter()
.copied()
.find(|&index| (index as usize) >= vertices.len())
.map(|index| Self::IndexPastVertices {
index,
vertices: vertices.len(),
});
let covered: u64 = slots.iter().map(|slot| u64::from(slot.index_count())).sum();
let uncovered = (covered != indices.len() as u64).then_some(Self::SlotsCoverage {
covered,
indices: indices.len(),
});
past.into_iter().chain(uncovered).collect()
}
}
impl fmt::Display for MeshError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IndexPastVertices { index, vertices } => {
write!(f, "has the index {index} past its {vertices} vertices")
}
Self::SlotsCoverage { covered, indices } => {
write!(
f,
"has slots covering {covered} indices where it holds {indices}"
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Color;
use crate::math::UVec2;
fn corners(count: usize) -> Vec<Vertex> {
vec![Vertex::new(Vec3::ZERO, Vec3::Y, Vec2::ZERO); count]
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Third {
First,
Second,
Last,
}
impl Part for Third {
fn from_name(_name: &str) -> Option<Self> {
None
}
fn all() -> Vec<Self> {
vec![Self::First, Self::Second, Self::Last]
}
fn index(&self) -> u32 {
*self as u32
}
}
#[test]
fn the_memory_a_mesh_holds_counts_its_corners_its_indices_and_its_pixels() {
let plain = MeshData::new(corners(6), (0..6).collect())
.erased()
.expect("built whole");
let painted = MeshData::new(corners(6), (0..6).collect())
.with_texture(TextureData::rgba8(UVec2::splat(2), vec![0; 16]))
.erased()
.expect("built whole");
assert_eq!(
plain.bytes(),
6 * size_of::<Vertex>() + 6 * size_of::<u32>()
);
assert_eq!(
painted.bytes(),
plain.bytes() + 16,
"and the texels it samples"
);
}
#[test]
fn a_mesh_in_parts_has_one_slot_per_part_in_the_order_they_count_themselves() {
let mesh = MeshData::in_parts(corners(6), (0..6).collect(), |part: Third| match part {
Third::First => Slot::new(3, Material::default()),
Third::Second => Slot::new(2, Material::color(Color::BLACK)),
Third::Last => Slot::new(1, Material::default()),
})
.erased()
.expect("built whole");
assert_eq!(mesh.part_count(), 3);
assert_eq!(mesh.part_indices(0), 0..3);
assert_eq!(mesh.part_indices(1), 3..5);
assert_eq!(mesh.part_indices(2), 5..6);
assert_eq!(
mesh.part_of(1),
Some(1),
"and each slot resolves to its part"
);
assert_eq!(mesh.part_material(1), Material::color(Color::BLACK));
}
#[test]
fn slots_that_do_not_cover_the_indices_exactly_are_an_error_and_draw_nothing() {
let short = |part: Third| {
Slot::new(
if part == Third::First { 2 } else { 0 },
Material::default(),
)
};
let mesh = MeshData::in_parts(corners(6), (0..6).collect(), short);
assert!(mesh.indices().is_empty(), "nothing of it is drawn");
let errors = mesh.erased().expect_err("the slots stop short");
assert_eq!(
errors,
vec![MeshError::SlotsCoverage {
covered: 2,
indices: 6
}]
);
assert_eq!(
errors[0].to_string(),
"has slots covering 2 indices where it holds 6"
);
}
#[test]
fn an_index_past_the_vertices_is_an_error() {
let mesh = MeshData::new(corners(2), vec![0, 1, 2]);
assert!(mesh.vertices().is_empty());
let errors = mesh.erased().expect_err("the last index reaches past");
assert_eq!(
errors,
vec![MeshError::IndexPastVertices {
index: 2,
vertices: 2
}]
);
assert_eq!(errors[0].to_string(), "has the index 2 past its 2 vertices");
}
#[test]
fn a_mesh_of_one_slot_has_no_part_to_name_it_by() {
let mesh = MeshData::new(corners(3), vec![0, 1, 2])
.with_material(Material::color(Color::BLACK))
.erased()
.expect("built whole");
assert_eq!(mesh.part_of(0), None);
assert_eq!(mesh.part_indices(0), 0..3);
assert_eq!(mesh.part_material(0), Material::color(Color::BLACK));
}
#[test]
fn the_maps_a_generated_mesh_is_built_with_are_drawn_from_its_slot() {
let pixels = |value| vec![value; 4];
let mesh = MeshData::new(corners(3), vec![0, 1, 2])
.with_shading(ShadingData::rgba8(UVec2::ONE, pixels(3)))
.with_emissive_map(TextureData::rgba8(UVec2::ONE, pixels(7)))
.erased()
.expect("built whole");
assert_eq!(
mesh.part_shading(0),
Some(&ShadingData::rgba8(UVec2::ONE, pixels(3)))
);
assert_eq!(
mesh.part_emissive(0),
Some(&TextureData::rgba8(UVec2::ONE, pixels(7)))
);
}
#[test]
fn a_mesh_with_nothing_in_it_draws_no_parts() {
let mesh = MeshData::<NoParts>::empty()
.erased()
.expect("drawing nothing is no error");
assert_eq!(mesh.part_count(), 0);
}
}