Skip to main content

bunny_codec/
compressed.rs

1mod error;
2mod read;
3mod view;
4
5use read::{checked_add, checked_payload_len, parse_header, take, validate_triangles};
6
7pub use error::CompressedMeshError;
8pub use view::{CompressedIndexWidth, CompressedMesh, CompressedTriangle};
9
10const HEADER_LEN: usize = 76;
11const VERTEX_STRIDE: usize = 6;
12const TRIANGLE16_STRIDE: usize = 6;
13const TRIANGLE32_STRIDE: usize = 12;
14const MAX_VERTICES: usize = 1_000_000;
15const MAX_TRIANGLES: usize = 1_000_000;
16
17/// Decodes a canonical Bunny compressed mesh byte stream.
18///
19/// The accepted profile is documented in `docs/goalposts/v0.4.0-gp3.md`.
20/// Decoding borrows the vertex and triangle byte sections, validates all
21/// triangle indices, and performs no heap allocation on the accepted path.
22///
23/// # Errors
24/// Returns `CompressedMeshError` when any header, length, bounds, count, or
25/// payload invariant is violated.
26pub fn decode_compressed_mesh(input: &[u8]) -> Result<CompressedMesh<'_>, CompressedMeshError> {
27    let header = parse_header(input)?;
28    let vertex_len = checked_payload_len(header.vertex_count, VERTEX_STRIDE)?;
29    let triangle_len = checked_payload_len(header.triangle_count, header.index_width.stride())?;
30    let expected_len = checked_add(vertex_len, triangle_len)?;
31    let expected_len_u64 =
32        u64::try_from(expected_len).map_err(|_| CompressedMeshError::IntegerOverflow)?;
33    let _declared_end = u64::try_from(HEADER_LEN)
34        .map_err(|_| CompressedMeshError::IntegerOverflow)?
35        .checked_add(header.payload_len)
36        .ok_or(CompressedMeshError::IntegerOverflow)?;
37    if header.payload_len != expected_len_u64 {
38        return Err(CompressedMeshError::InvalidPayloadLength);
39    }
40    let payload_end = checked_add(HEADER_LEN, expected_len)?;
41    if input.len() < payload_end {
42        return Err(CompressedMeshError::PayloadTooShort);
43    }
44    if input.len() != payload_end {
45        return Err(CompressedMeshError::TrailingData);
46    }
47
48    let vertex_bytes = take(input, HEADER_LEN, vertex_len)?;
49    let triangle_start = checked_add(HEADER_LEN, vertex_len)?;
50    let triangle_bytes = take(input, triangle_start, triangle_len)?;
51    validate_triangles(
52        triangle_bytes,
53        header.triangle_count,
54        header.vertex_count,
55        header.index_width,
56    )?;
57    Ok(CompressedMesh::new(
58        header.bounds,
59        vertex_bytes,
60        triangle_bytes,
61        header.vertex_count,
62        header.triangle_count,
63        header.index_width,
64    ))
65}