1use std::io::Read;
4
5use super::{
6 binary, checked_mul, Bwm, BwmAabbNode, BwmAdjacency, BwmBinaryError, BwmEdge, BwmFace,
7 BwmTypeCode, BwmVec3, AABB_ENTRY_SIZE, ADJACENCY_ENTRY_SIZE, BWM_MAGIC, BWM_VERSION_V10,
8 DISTANCE_ENTRY_SIZE, EDGE_ENTRY_SIZE, FACE_INDEX_ENTRY_SIZE, FILE_HEADER_SIZE,
9 MATERIAL_ENTRY_SIZE, NORMAL_ENTRY_SIZE, PERIMETER_ENTRY_SIZE, VERTEX_ENTRY_SIZE,
10};
11
12#[cfg_attr(
14 feature = "tracing",
15 tracing::instrument(level = "debug", skip(reader))
16)]
17pub fn read_bwm<R: Read>(reader: &mut R) -> Result<Bwm, BwmBinaryError> {
18 let mut bytes = Vec::new();
19 reader.read_to_end(&mut bytes)?;
20 read_bwm_from_bytes(&bytes)
21}
22
23#[cfg_attr(
25 feature = "tracing",
26 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
27)]
28pub fn read_bwm_from_bytes(bytes: &[u8]) -> Result<Bwm, BwmBinaryError> {
29 if bytes.len() < FILE_HEADER_SIZE {
30 return Err(BwmBinaryError::InvalidHeader(
31 "file smaller than BWM header".into(),
32 ));
33 }
34
35 let magic = binary::read_fourcc(bytes, 0)?;
36 binary::expect_fourcc(magic, BWM_MAGIC).map_err(BwmBinaryError::InvalidMagic)?;
37 let version = binary::read_fourcc(bytes, 4)?;
38 binary::expect_fourcc(version, BWM_VERSION_V10).map_err(BwmBinaryError::InvalidVersion)?;
39
40 let walkmesh_type = BwmTypeCode::from_raw(binary::read_u32(bytes, 8)?); let relative_hook1 = read_vec3(bytes, 12)?; let relative_hook2 = read_vec3(bytes, 24)?; let absolute_hook1 = read_vec3(bytes, 36)?; let absolute_hook2 = read_vec3(bytes, 48)?; let position = read_vec3(bytes, 60)?; let vertex_count = binary::checked_to_usize(binary::read_u32(bytes, 72)?, "vertex_count")?; let vertex_offset = binary::checked_to_usize(binary::read_u32(bytes, 76)?, "vertex_offset")?; let face_count = binary::checked_to_usize(binary::read_u32(bytes, 80)?, "face_count")?; let face_indices_offset =
53 binary::checked_to_usize(binary::read_u32(bytes, 84)?, "face_indices_offset")?; let materials_offset =
55 binary::checked_to_usize(binary::read_u32(bytes, 88)?, "materials_offset")?; let normals_offset = binary::checked_to_usize(binary::read_u32(bytes, 92)?, "normals_offset")?; let planar_distances_offset =
58 binary::checked_to_usize(binary::read_u32(bytes, 96)?, "planar_distances_offset")?; let aabb_count = binary::checked_to_usize(binary::read_u32(bytes, 100)?, "aabb_count")?; let aabb_offset = binary::checked_to_usize(binary::read_u32(bytes, 104)?, "aabb_offset")?; let unknown = binary::read_u32(bytes, 108)?; let adjacency_count =
63 binary::checked_to_usize(binary::read_u32(bytes, 112)?, "adjacency_count")?; let adjacency_offset =
65 binary::checked_to_usize(binary::read_u32(bytes, 116)?, "adjacency_offset")?; let edge_count = binary::checked_to_usize(binary::read_u32(bytes, 120)?, "edge_count")?; let edges_offset = binary::checked_to_usize(binary::read_u32(bytes, 124)?, "edges_offset")?; let perimeter_count =
69 binary::checked_to_usize(binary::read_u32(bytes, 128)?, "perimeter_count")?; let perimeters_offset =
71 binary::checked_to_usize(binary::read_u32(bytes, 132)?, "perimeters_offset")?; let vertices_size = checked_mul(vertex_count, VERTEX_ENTRY_SIZE, "vertices_size")?;
75 binary::check_slice_in_bounds(bytes, vertex_offset, vertices_size, "vertices")?;
76 let face_indices_size = checked_mul(face_count, FACE_INDEX_ENTRY_SIZE, "face_indices_size")?;
77 binary::check_slice_in_bounds(
78 bytes,
79 face_indices_offset,
80 face_indices_size,
81 "face_indices",
82 )?;
83 let materials_size = checked_mul(face_count, MATERIAL_ENTRY_SIZE, "materials_size")?;
84 binary::check_slice_in_bounds(bytes, materials_offset, materials_size, "materials")?;
85 let normals_size = checked_mul(face_count, NORMAL_ENTRY_SIZE, "normals_size")?;
86 binary::check_slice_in_bounds(bytes, normals_offset, normals_size, "normals")?;
87 let distances_size = checked_mul(face_count, DISTANCE_ENTRY_SIZE, "distances_size")?;
88 binary::check_slice_in_bounds(
89 bytes,
90 planar_distances_offset,
91 distances_size,
92 "planar_distances",
93 )?;
94 let aabb_size = checked_mul(aabb_count, AABB_ENTRY_SIZE, "aabb_size")?;
95 binary::check_slice_in_bounds(bytes, aabb_offset, aabb_size, "aabb_nodes")?;
96 let adjacency_size = checked_mul(adjacency_count, ADJACENCY_ENTRY_SIZE, "adjacency_size")?;
97 binary::check_slice_in_bounds(bytes, adjacency_offset, adjacency_size, "adjacencies")?;
98 let edge_size = checked_mul(edge_count, EDGE_ENTRY_SIZE, "edge_size")?;
99 binary::check_slice_in_bounds(bytes, edges_offset, edge_size, "edges")?;
100 let perimeter_size = checked_mul(perimeter_count, PERIMETER_ENTRY_SIZE, "perimeter_size")?;
101 binary::check_slice_in_bounds(bytes, perimeters_offset, perimeter_size, "perimeters")?;
102
103 let mut vertices = Vec::with_capacity(vertex_count);
105 for index in 0..vertex_count {
106 let base = vertex_offset + index * VERTEX_ENTRY_SIZE;
107 vertices.push(read_vec3(bytes, base)?);
108 }
109
110 let mut faces = Vec::with_capacity(face_count);
112 for index in 0..face_count {
113 let indices_base = face_indices_offset + index * FACE_INDEX_ENTRY_SIZE;
114 let i1 = binary::read_u32(bytes, indices_base)?;
115 let i2 = binary::read_u32(bytes, indices_base + 4)?;
116 let i3 = binary::read_u32(bytes, indices_base + 8)?;
117 for (slot, value) in [i1, i2, i3].iter().enumerate() {
118 let vertex_index = binary::checked_to_usize(*value, "face_vertex_index")?;
119 if vertex_index >= vertices.len() {
120 return Err(BwmBinaryError::InvalidData(format!(
121 "faces[{index}].vertex_indices[{slot}]={value} out of bounds for {} vertices",
122 vertices.len()
123 )));
124 }
125 }
126
127 let material_base = materials_offset + index * MATERIAL_ENTRY_SIZE;
128 let material_id = binary::read_u32(bytes, material_base)?;
129 let normal_base = normals_offset + index * NORMAL_ENTRY_SIZE;
130 let normal = read_vec3(bytes, normal_base)?;
131 let distance_base = planar_distances_offset + index * DISTANCE_ENTRY_SIZE;
132 let planar_distance = binary::read_f32(bytes, distance_base)?;
133
134 faces.push(BwmFace {
135 vertex_indices: [i1, i2, i3],
136 material_id,
137 normal,
138 planar_distance,
139 });
140 }
141
142 let mut aabb_nodes = Vec::with_capacity(aabb_count);
144 for index in 0..aabb_count {
145 let base = aabb_offset + index * AABB_ENTRY_SIZE;
146 aabb_nodes.push(BwmAabbNode {
147 bb_min: read_vec3(bytes, base)?,
148 bb_max: read_vec3(bytes, base + 12)?,
149 face_index: binary::read_u32(bytes, base + 24)?,
150 unknown: binary::read_u32(bytes, base + 28)?,
151 split_axis: binary::read_u32(bytes, base + 32)?,
152 left_child: binary::read_u32(bytes, base + 36)?,
153 right_child: binary::read_u32(bytes, base + 40)?,
154 });
155 }
156
157 let mut adjacencies = Vec::with_capacity(adjacency_count);
159 for index in 0..adjacency_count {
160 let base = adjacency_offset + index * ADJACENCY_ENTRY_SIZE;
161 adjacencies.push(BwmAdjacency {
162 edge_refs: [
163 read_i32(bytes, base)?,
164 read_i32(bytes, base + 4)?,
165 read_i32(bytes, base + 8)?,
166 ],
167 });
168 }
169
170 let mut edges = Vec::with_capacity(edge_count);
171 for index in 0..edge_count {
172 let base = edges_offset + index * EDGE_ENTRY_SIZE;
173 edges.push(BwmEdge {
174 edge_index: read_i32(bytes, base)?,
175 transition: read_i32(bytes, base + 4)?,
176 });
177 }
178
179 let mut perimeters = Vec::with_capacity(perimeter_count);
180 for index in 0..perimeter_count {
181 let base = perimeters_offset + index * PERIMETER_ENTRY_SIZE;
182 perimeters.push(binary::read_u32(bytes, base)?);
183 }
184
185 Ok(Bwm {
186 walkmesh_type,
187 relative_hook1,
188 relative_hook2,
189 absolute_hook1,
190 absolute_hook2,
191 position,
192 unknown,
193 vertices,
194 faces,
195 aabb_nodes,
196 adjacencies,
197 edges,
198 perimeters,
199 })
200}
201
202fn read_i32(bytes: &[u8], offset: usize) -> Result<i32, BwmBinaryError> {
203 let raw = binary::read_u32(bytes, offset)?;
204 Ok(i32::from_le_bytes(raw.to_le_bytes()))
205}
206
207fn read_vec3(bytes: &[u8], offset: usize) -> Result<BwmVec3, BwmBinaryError> {
208 Ok(BwmVec3 {
209 x: binary::read_f32(bytes, offset)?,
210 y: binary::read_f32(bytes, offset + 4)?,
211 z: binary::read_f32(bytes, offset + 8)?,
212 })
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::bwm::{write_bwm_to_vec, BwmType};
219
220 const TEST_WOK: &[u8] = include_bytes!(concat!(
221 env!("CARGO_MANIFEST_DIR"),
222 "/../../fixtures/test.wok"
223 ));
224 const TOOLSET_WOK: &[u8] = include_bytes!(concat!(
225 env!("CARGO_MANIFEST_DIR"),
226 "/../../fixtures/zio006j.wok"
227 ));
228
229 fn synthetic_bwm() -> Bwm {
230 Bwm {
231 walkmesh_type: BwmTypeCode::from(BwmType::AreaModel),
232 relative_hook1: BwmVec3::new(1.0, 2.0, 3.0),
233 relative_hook2: BwmVec3::new(4.0, 5.0, 6.0),
234 absolute_hook1: BwmVec3::new(7.0, 8.0, 9.0),
235 absolute_hook2: BwmVec3::new(10.0, 11.0, 12.0),
236 position: BwmVec3::new(13.0, 14.0, 15.0),
237 unknown: 4,
238 vertices: vec![
239 BwmVec3::new(0.0, 0.0, 0.0),
240 BwmVec3::new(1.0, 0.0, 0.0),
241 BwmVec3::new(0.0, 1.0, 0.0),
242 ],
243 faces: vec![BwmFace {
244 vertex_indices: [0, 1, 2],
245 material_id: 1,
246 normal: BwmVec3::new(0.0, 0.0, 1.0),
247 planar_distance: 0.0,
248 }],
249 aabb_nodes: vec![BwmAabbNode {
250 bb_min: BwmVec3::new(0.0, 0.0, 0.0),
251 bb_max: BwmVec3::new(1.0, 1.0, 0.0),
252 face_index: 0,
253 unknown: 4,
254 split_axis: 0,
255 left_child: 0xFFFF_FFFF,
256 right_child: 0xFFFF_FFFF,
257 }],
258 adjacencies: vec![BwmAdjacency {
259 edge_refs: [-1, -1, -1],
260 }],
261 edges: vec![BwmEdge {
262 edge_index: 0,
263 transition: -1,
264 }],
265 perimeters: vec![1],
266 }
267 }
268
269 #[test]
270 fn roundtrip_synthetic_bwm() {
271 let bwm = synthetic_bwm();
272 let bytes = write_bwm_to_vec(&bwm).expect("write should succeed");
273 let parsed = read_bwm_from_bytes(&bytes).expect("read should succeed");
274 assert_eq!(parsed, bwm);
275 }
276
277 #[test]
278 fn writer_is_deterministic_for_synthetic_bwm() {
279 let bwm = synthetic_bwm();
280 let first = write_bwm_to_vec(&bwm).expect("first write should succeed");
281 let second = write_bwm_to_vec(&bwm).expect("second write should succeed");
282 assert_eq!(first, second);
283 }
284
285 #[test]
286 fn parses_wok_fixture() {
287 let bwm = read_bwm_from_bytes(TEST_WOK).expect("fixture should parse");
288 assert_eq!(bwm.walkmesh_type.known(), Some(BwmType::AreaModel));
289 assert_eq!(bwm.vertices.len(), 6);
290 assert_eq!(bwm.faces.len(), 4);
291 assert!(!bwm.aabb_nodes.is_empty());
292 }
293
294 #[test]
295 fn parses_toolset_wok_fixture() {
296 let bwm = read_bwm_from_bytes(TOOLSET_WOK).expect("fixture should parse");
297 assert_eq!(bwm.walkmesh_type.known(), Some(BwmType::AreaModel));
298 assert_eq!(bwm.vertices.len(), 4);
299 assert_eq!(bwm.faces.len(), 2);
300 }
301
302 #[test]
303 fn read_write_roundtrip_preserves_fixture_semantics() {
304 let parsed = read_bwm_from_bytes(TEST_WOK).expect("fixture should parse");
305 let bytes = write_bwm_to_vec(&parsed).expect("write should succeed");
306 let reparsed = read_bwm_from_bytes(&bytes).expect("re-read should succeed");
307 assert_eq!(reparsed, parsed);
308 }
309
310 #[test]
311 fn rejects_invalid_magic() {
312 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
313 bytes[0..4].copy_from_slice(b"NOPE");
314 bytes[4..8].copy_from_slice(&BWM_VERSION_V10);
315 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
316 assert!(matches!(err, BwmBinaryError::InvalidMagic(_)));
317 }
318
319 #[test]
320 fn rejects_invalid_version() {
321 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
322 bytes[0..4].copy_from_slice(&BWM_MAGIC);
323 bytes[4..8].copy_from_slice(b"V9.9");
324 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
325 assert!(matches!(err, BwmBinaryError::InvalidVersion(_)));
326 }
327
328 #[test]
329 fn rejects_truncated_header() {
330 let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
331 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
332 assert!(matches!(err, BwmBinaryError::InvalidHeader(_)));
333 }
334
335 #[test]
336 fn rejects_out_of_bounds_vertex_table() {
337 let mut bytes = write_bwm_to_vec(&synthetic_bwm()).expect("write");
338 bytes[76..80].copy_from_slice(&0xFFFF_FFFF_u32.to_le_bytes()); let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
340 assert!(matches!(err, BwmBinaryError::InvalidHeader(_)));
341 }
342
343 #[test]
344 fn rejects_face_vertex_index_out_of_bounds() {
345 let mut bytes = write_bwm_to_vec(&synthetic_bwm()).expect("write");
346 let face_indices_offset = u32::from_le_bytes([bytes[84], bytes[85], bytes[86], bytes[87]]);
348 let face_indices_offset = usize::try_from(face_indices_offset).expect("offset fits");
349 bytes[face_indices_offset..face_indices_offset + 4].copy_from_slice(&99_u32.to_le_bytes());
350
351 let err = read_bwm_from_bytes(&bytes).expect_err("must fail");
352 assert!(matches!(err, BwmBinaryError::InvalidData(_)));
353 }
354
355 #[test]
356 fn decode_encode_traits_roundtrip() {
357 use crate::binary::{DecodeBinary, EncodeBinary};
358 let bwm = synthetic_bwm();
359 let bytes = bwm.encode_binary().expect("encode");
360 let decoded = Bwm::decode_binary(&bytes).expect("decode");
361 assert_eq!(decoded, bwm);
362 }
363}