1use num_enum::{IntoPrimitive, TryFromPrimitive};
47use thiserror::Error;
48
49use crate::binary::{self, DecodeBinary, EncodeBinary};
50
51pub mod ascii_reader;
53pub mod ascii_writer;
55mod reader;
56mod writer;
57
58pub use ascii_reader::{read_bwm_ascii, BwmAsciiError};
59pub use ascii_writer::write_bwm_ascii;
60pub use reader::{read_bwm, read_bwm_from_bytes};
61pub use writer::{write_bwm, write_bwm_to_vec};
62
63pub(super) const FILE_HEADER_SIZE: usize = 136;
65pub(super) const VERTEX_ENTRY_SIZE: usize = 12;
67pub(super) const FACE_INDEX_ENTRY_SIZE: usize = 12;
69pub(super) const MATERIAL_ENTRY_SIZE: usize = 4;
71pub(super) const NORMAL_ENTRY_SIZE: usize = 12;
73pub(super) const DISTANCE_ENTRY_SIZE: usize = 4;
75pub(super) const AABB_ENTRY_SIZE: usize = 44;
77pub(super) const ADJACENCY_ENTRY_SIZE: usize = 12;
79pub(super) const EDGE_ENTRY_SIZE: usize = 8;
81pub(super) const PERIMETER_ENTRY_SIZE: usize = 4;
83pub(super) const BWM_MAGIC: [u8; 4] = *b"BWM ";
85pub(super) const BWM_VERSION_V10: [u8; 4] = *b"V1.0";
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
92#[repr(u32)]
93pub enum SurfaceMaterial {
94 Undefined = 0,
96 Dirt = 1,
98 Obscuring = 2,
100 Stone = 3,
102 Wood = 4,
104 Water = 5,
106 NonWalk = 6,
108 Transparent = 7,
110 Carpet = 8,
112 Metal = 9,
114 Puddles = 10,
116 Swamp = 11,
118 Mud = 12,
120 Leaves = 13,
122 Lava = 14,
124 BottomlessPit = 15,
126 DeepWater = 16,
128 Door = 17,
130 NonWalkGrass = 18,
132 NonWalkStone = 19,
134 NonWalkWood = 20,
136 NonWalkWater = 21,
138 NonWalkGlass = 22,
140 NonWalkCarpet = 23,
142 NonWalkMetal = 24,
144 NonWalkPuddles = 25,
146 NonWalkSwamp = 26,
148 NonWalkMud = 27,
150 NonWalkLeaves = 28,
152 NonWalkLava = 29,
154 NonWalkBottomlessPit = 30,
156}
157
158impl SurfaceMaterial {
159 pub fn is_walkable(self) -> bool {
163 !matches!(
164 self,
165 Self::NonWalk
166 | Self::BottomlessPit
167 | Self::DeepWater
168 | Self::NonWalkGrass
169 | Self::NonWalkStone
170 | Self::NonWalkWood
171 | Self::NonWalkWater
172 | Self::NonWalkGlass
173 | Self::NonWalkCarpet
174 | Self::NonWalkMetal
175 | Self::NonWalkPuddles
176 | Self::NonWalkSwamp
177 | Self::NonWalkMud
178 | Self::NonWalkLeaves
179 | Self::NonWalkLava
180 | Self::NonWalkBottomlessPit
181 )
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
187#[repr(u32)]
188pub enum BwmType {
189 PlaceableOrDoor = 0,
191 AreaModel = 1,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
197pub struct BwmTypeCode(u32);
198
199impl BwmTypeCode {
200 pub const fn from_raw(raw: u32) -> Self {
202 Self(raw)
203 }
204
205 pub const fn raw(self) -> u32 {
207 self.0
208 }
209
210 pub fn known(self) -> Option<BwmType> {
212 BwmType::try_from(self.0).ok()
213 }
214}
215
216impl From<BwmType> for BwmTypeCode {
217 fn from(value: BwmType) -> Self {
218 Self(u32::from(value))
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq)]
224pub struct BwmVec3 {
225 pub x: f32,
227 pub y: f32,
229 pub z: f32,
231}
232
233impl BwmVec3 {
234 pub const fn new(x: f32, y: f32, z: f32) -> Self {
236 Self { x, y, z }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq)]
242pub struct BwmFace {
243 pub vertex_indices: [u32; 3],
245 pub material_id: u32,
247 pub normal: BwmVec3,
249 pub planar_distance: f32,
251}
252
253#[derive(Debug, Clone, PartialEq)]
255pub struct BwmAabbNode {
256 pub bb_min: BwmVec3,
258 pub bb_max: BwmVec3,
260 pub face_index: u32,
262 pub unknown: u32,
264 pub split_axis: u32,
266 pub left_child: u32,
268 pub right_child: u32,
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
274pub struct BwmAdjacency {
275 pub edge_refs: [i32; 3],
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
281pub struct BwmEdge {
282 pub edge_index: i32,
284 pub transition: i32,
286}
287
288#[derive(Debug, Clone, PartialEq)]
290pub struct Bwm {
291 pub walkmesh_type: BwmTypeCode,
293 pub relative_hook1: BwmVec3,
295 pub relative_hook2: BwmVec3,
297 pub absolute_hook1: BwmVec3,
299 pub absolute_hook2: BwmVec3,
301 pub position: BwmVec3,
303 pub unknown: u32,
305 pub vertices: Vec<BwmVec3>,
307 pub faces: Vec<BwmFace>,
309 pub aabb_nodes: Vec<BwmAabbNode>,
311 pub adjacencies: Vec<BwmAdjacency>,
313 pub edges: Vec<BwmEdge>,
315 pub perimeters: Vec<u32>,
317}
318
319impl Default for Bwm {
320 fn default() -> Self {
321 Self {
322 walkmesh_type: BwmTypeCode::from(BwmType::AreaModel),
323 relative_hook1: BwmVec3::new(0.0, 0.0, 0.0),
324 relative_hook2: BwmVec3::new(0.0, 0.0, 0.0),
325 absolute_hook1: BwmVec3::new(0.0, 0.0, 0.0),
326 absolute_hook2: BwmVec3::new(0.0, 0.0, 0.0),
327 position: BwmVec3::new(0.0, 0.0, 0.0),
328 unknown: 0,
329 vertices: Vec::new(),
330 faces: Vec::new(),
331 aabb_nodes: Vec::new(),
332 adjacencies: Vec::new(),
333 edges: Vec::new(),
334 perimeters: Vec::new(),
335 }
336 }
337}
338
339impl Bwm {
340 pub fn new() -> Self {
342 Self::default()
343 }
344}
345
346impl DecodeBinary for Bwm {
347 type Error = BwmBinaryError;
348
349 fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
350 read_bwm_from_bytes(bytes)
351 }
352}
353
354impl EncodeBinary for Bwm {
355 type Error = BwmBinaryError;
356
357 fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
358 write_bwm_to_vec(self)
359 }
360}
361
362#[derive(Debug, Error)]
364pub enum BwmBinaryError {
365 #[error(transparent)]
367 Io(#[from] std::io::Error),
368 #[error("invalid BWM magic: {0:?}")]
370 InvalidMagic([u8; 4]),
371 #[error("invalid BWM version: {0:?}")]
373 InvalidVersion([u8; 4]),
374 #[error("invalid BWM header: {0}")]
376 InvalidHeader(String),
377 #[error("invalid BWM data: {0}")]
379 InvalidData(String),
380 #[error("value overflow while writing field `{0}`")]
382 ValueOverflow(&'static str),
383}
384
385impl From<binary::BinaryLayoutError> for BwmBinaryError {
386 fn from(error: binary::BinaryLayoutError) -> Self {
387 Self::InvalidHeader(error.to_string())
388 }
389}
390
391pub(super) fn checked_mul(
392 lhs: usize,
393 rhs: usize,
394 field: &'static str,
395) -> Result<usize, BwmBinaryError> {
396 lhs.checked_mul(rhs)
397 .ok_or(BwmBinaryError::ValueOverflow(field))
398}
399
400pub(super) fn checked_add(
401 lhs: usize,
402 rhs: usize,
403 field: &'static str,
404) -> Result<usize, BwmBinaryError> {
405 lhs.checked_add(rhs)
406 .ok_or(BwmBinaryError::ValueOverflow(field))
407}
408
409pub(super) fn usize_to_u32(value: usize, field: &'static str) -> Result<u32, BwmBinaryError> {
410 u32::try_from(value).map_err(|_| BwmBinaryError::ValueOverflow(field))
411}