ferravex_graphics/
cooked.rs

1//! Original portable little-endian mesh format; decode validates before cache insertion.
2use crate::{Color, Mesh, MeshError, Vec3, Vertex};
3impl Mesh {
4    pub fn to_cooked(&self) -> Result<Vec<u8>, MeshError> {
5        self.validate()?;
6        let mut out = b"GMSH0001".to_vec();
7        out.extend((self.vertices.len() as u32).to_le_bytes());
8        out.extend((self.indices.len() as u32).to_le_bytes());
9        for v in &self.vertices {
10            for f in v
11                .position
12                .to_array()
13                .into_iter()
14                .chain(v.normal.to_array())
15                .chain(v.color.to_array())
16            {
17                out.extend(f.to_le_bytes());
18            }
19        }
20        for i in &self.indices {
21            out.extend(i.to_le_bytes());
22        }
23        Ok(out)
24    }
25    pub fn from_cooked(bytes: &[u8]) -> Result<Self, MeshError> {
26        if bytes.len() < 16 || &bytes[..8] != b"GMSH0001" {
27            return Err(MeshError::InvalidTriangleCount);
28        }
29        let vertices = u32::from_le_bytes(bytes[8..12].try_into().unwrap()) as usize;
30        let indices = u32::from_le_bytes(bytes[12..16].try_into().unwrap()) as usize;
31        if vertices > 1_000_000 || indices > 6_000_000 {
32            return Err(MeshError::TooLarge);
33        }
34        if bytes.len() != 16 + vertices * 40 + indices * 4 {
35            return Err(MeshError::InvalidTriangleCount);
36        }
37        let mut result = Mesh {
38            vertices: Vec::with_capacity(vertices),
39            indices: Vec::with_capacity(indices),
40        };
41        for row in bytes[16..16 + vertices * 40].as_chunks::<40>().0 {
42            let f = |i: usize| f32::from_le_bytes(row[i * 4..i * 4 + 4].try_into().unwrap());
43            result.vertices.push(Vertex::new(
44                Vec3::new(f(0), f(1), f(2)),
45                Vec3::new(f(3), f(4), f(5)),
46                Color::new(f(6), f(7), f(8), f(9)),
47            ));
48        }
49        for row in bytes[16 + vertices * 40..].as_chunks::<4>().0 {
50            result.indices.push(u32::from_le_bytes(*row));
51        }
52        result.validate()?;
53        Ok(result)
54    }
55}
56#[cfg(test)]
57mod tests {
58    use super::*;
59    #[test]
60    fn cooked_mesh_roundtrip_rejects_truncation_bad_counts_indices_and_floats() {
61        let mesh = Mesh::cuboid(Vec3::ONE, Color::from_hex(0xcabb99));
62        let bytes = mesh.to_cooked().unwrap();
63        assert_eq!(Mesh::from_cooked(&bytes).unwrap(), mesh);
64        for end in 0..bytes.len() {
65            assert!(Mesh::from_cooked(&bytes[..end]).is_err());
66        }
67        let mut bad = bytes.clone();
68        bad[16..20].copy_from_slice(&f32::NAN.to_le_bytes());
69        assert_eq!(Mesh::from_cooked(&bad), Err(MeshError::NonFiniteVertex));
70        let mut bad = bytes.clone();
71        let last = bad.len() - 4;
72        bad[last..].copy_from_slice(&u32::MAX.to_le_bytes());
73        assert_eq!(Mesh::from_cooked(&bad), Err(MeshError::IndexOutOfBounds));
74        let mut bad = bytes;
75        bad[8..12].copy_from_slice(&u32::MAX.to_le_bytes());
76        assert_eq!(Mesh::from_cooked(&bad), Err(MeshError::TooLarge));
77    }
78}