Skip to main content

embedded_3dgfx/
scene_format.rs

1use crate::error::RenderError;
2
3pub const SCENE_MAGIC: [u8; 4] = *b"E3DS";
4pub const SCENE_VERSION: u16 = 1;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ChunkKind {
8    Mesh = 1,
9    Texture = 2,
10    Meta = 3,
11}
12
13impl ChunkKind {
14    fn from_u16(v: u16) -> Option<Self> {
15        match v {
16            1 => Some(Self::Mesh),
17            2 => Some(Self::Texture),
18            3 => Some(Self::Meta),
19            _ => None,
20        }
21    }
22}
23
24#[derive(Debug, Clone, Copy)]
25pub struct EncodedChunk<'a> {
26    pub kind: ChunkKind,
27    pub payload: &'a [u8],
28}
29
30#[derive(Debug, Clone, Copy)]
31pub struct SceneChunkRef<'a> {
32    pub kind: ChunkKind,
33    pub payload: &'a [u8],
34}
35
36pub fn encode_scene<const N: usize>(
37    chunks: &[EncodedChunk<'_>],
38) -> Result<heapless::Vec<u8, N>, RenderError> {
39    let mut out = heapless::Vec::<u8, N>::new();
40    for b in SCENE_MAGIC {
41        out.push(b)
42            .map_err(|_| RenderError::InvalidInput("scene buffer too small"))?;
43    }
44    for b in SCENE_VERSION.to_le_bytes() {
45        out.push(b)
46            .map_err(|_| RenderError::InvalidInput("scene buffer too small"))?;
47    }
48    for b in (chunks.len() as u16).to_le_bytes() {
49        out.push(b)
50            .map_err(|_| RenderError::InvalidInput("scene buffer too small"))?;
51    }
52    for chunk in chunks {
53        for b in (chunk.kind as u16).to_le_bytes() {
54            out.push(b)
55                .map_err(|_| RenderError::InvalidInput("scene buffer too small"))?;
56        }
57        for b in (chunk.payload.len() as u32).to_le_bytes() {
58            out.push(b)
59                .map_err(|_| RenderError::InvalidInput("scene buffer too small"))?;
60        }
61        for b in chunk.payload {
62            out.push(*b)
63                .map_err(|_| RenderError::InvalidInput("scene buffer too small"))?;
64        }
65    }
66    Ok(out)
67}
68
69pub struct ChunkCursor<'a> {
70    bytes: &'a [u8],
71    offset: usize,
72    remaining_chunks: u16,
73}
74
75impl<'a> ChunkCursor<'a> {
76    pub fn new(bytes: &'a [u8]) -> Result<Self, RenderError> {
77        if bytes.len() < 8 {
78            return Err(RenderError::InvalidInput("scene too small"));
79        }
80        if bytes[0..4] != SCENE_MAGIC {
81            return Err(RenderError::InvalidInput("invalid scene magic"));
82        }
83        let version = u16::from_le_bytes([bytes[4], bytes[5]]);
84        if version != SCENE_VERSION {
85            return Err(RenderError::InvalidInput("unsupported scene version"));
86        }
87        let chunk_count = u16::from_le_bytes([bytes[6], bytes[7]]);
88        Ok(Self {
89            bytes,
90            offset: 8,
91            remaining_chunks: chunk_count,
92        })
93    }
94
95    pub fn next_chunk(&mut self) -> Result<Option<SceneChunkRef<'a>>, RenderError> {
96        if self.remaining_chunks == 0 {
97            return Ok(None);
98        }
99        if self.offset + 6 > self.bytes.len() {
100            return Err(RenderError::InvalidInput("truncated scene chunk header"));
101        }
102        let kind_raw = u16::from_le_bytes([self.bytes[self.offset], self.bytes[self.offset + 1]]);
103        let len = u32::from_le_bytes([
104            self.bytes[self.offset + 2],
105            self.bytes[self.offset + 3],
106            self.bytes[self.offset + 4],
107            self.bytes[self.offset + 5],
108        ]) as usize;
109        self.offset += 6;
110        if self.offset + len > self.bytes.len() {
111            return Err(RenderError::InvalidInput("truncated scene chunk payload"));
112        }
113        let payload = &self.bytes[self.offset..self.offset + len];
114        self.offset += len;
115        self.remaining_chunks -= 1;
116        let kind =
117            ChunkKind::from_u16(kind_raw).ok_or(RenderError::InvalidInput("unknown chunk kind"))?;
118        Ok(Some(SceneChunkRef { kind, payload }))
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn encode_decode_scene_roundtrip() {
128        let chunks = [
129            EncodedChunk {
130                kind: ChunkKind::Mesh,
131                payload: &[1, 2, 3],
132            },
133            EncodedChunk {
134                kind: ChunkKind::Texture,
135                payload: &[4, 5],
136            },
137        ];
138        let bytes = encode_scene::<128>(&chunks).unwrap();
139        let mut cursor = ChunkCursor::new(&bytes).unwrap();
140        let c1 = cursor.next_chunk().unwrap().unwrap();
141        assert_eq!(c1.kind, ChunkKind::Mesh);
142        assert_eq!(c1.payload, &[1, 2, 3]);
143        let c2 = cursor.next_chunk().unwrap().unwrap();
144        assert_eq!(c2.kind, ChunkKind::Texture);
145        assert_eq!(c2.payload, &[4, 5]);
146        assert!(cursor.next_chunk().unwrap().is_none());
147    }
148}