embedded_3dgfx/
scene_format.rs1use 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}