1use num_enum::{IntoPrimitive, TryFromPrimitive};
38use thiserror::Error;
39
40use crate::binary::{self, DecodeBinary, EncodeBinary};
41
42pub mod ascii_reader;
44pub mod ascii_writer;
46mod reader;
47mod writer;
48
49pub use ascii_reader::{read_bwm_ascii, BwmAsciiError};
50pub use ascii_writer::write_bwm_ascii;
51pub use reader::{read_bwm, read_bwm_from_bytes};
52pub use writer::{write_bwm, write_bwm_to_vec};
53
54pub(super) const FILE_HEADER_SIZE: usize = 136;
56pub(super) const VERTEX_ENTRY_SIZE: usize = 12;
58pub(super) const FACE_INDEX_ENTRY_SIZE: usize = 12;
60pub(super) const MATERIAL_ENTRY_SIZE: usize = 4;
62pub(super) const NORMAL_ENTRY_SIZE: usize = 12;
64pub(super) const DISTANCE_ENTRY_SIZE: usize = 4;
66pub(super) const AABB_ENTRY_SIZE: usize = 44;
68pub(super) const ADJACENCY_ENTRY_SIZE: usize = 12;
70pub(super) const EDGE_ENTRY_SIZE: usize = 8;
72pub(super) const PERIMETER_ENTRY_SIZE: usize = 4;
74pub(super) const BWM_MAGIC: [u8; 4] = *b"BWM ";
76pub(super) const BWM_VERSION_V10: [u8; 4] = *b"V1.0";
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
83#[repr(u32)]
84pub enum SurfaceMaterial {
85 Undefined = 0,
87 Dirt = 1,
89 Obscuring = 2,
91 Stone = 3,
93 Wood = 4,
95 Water = 5,
97 NonWalk = 6,
99 Transparent = 7,
101 Carpet = 8,
103 Metal = 9,
105 Puddles = 10,
107 Swamp = 11,
109 Mud = 12,
111 Leaves = 13,
113 Lava = 14,
115 BottomlessPit = 15,
117 DeepWater = 16,
119 Door = 17,
121 NonWalkGrass = 18,
123 NonWalkStone = 19,
125 NonWalkWood = 20,
127 NonWalkWater = 21,
129 NonWalkGlass = 22,
131 NonWalkCarpet = 23,
133 NonWalkMetal = 24,
135 NonWalkPuddles = 25,
137 NonWalkSwamp = 26,
139 NonWalkMud = 27,
141 NonWalkLeaves = 28,
143 NonWalkLava = 29,
145 NonWalkBottomlessPit = 30,
147}
148
149impl SurfaceMaterial {
150 pub fn is_walkable(self) -> bool {
154 !matches!(
155 self,
156 Self::NonWalk
157 | Self::BottomlessPit
158 | Self::DeepWater
159 | Self::NonWalkGrass
160 | Self::NonWalkStone
161 | Self::NonWalkWood
162 | Self::NonWalkWater
163 | Self::NonWalkGlass
164 | Self::NonWalkCarpet
165 | Self::NonWalkMetal
166 | Self::NonWalkPuddles
167 | Self::NonWalkSwamp
168 | Self::NonWalkMud
169 | Self::NonWalkLeaves
170 | Self::NonWalkLava
171 | Self::NonWalkBottomlessPit
172 )
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
178#[repr(u32)]
179pub enum BwmType {
180 PlaceableOrDoor = 0,
182 AreaModel = 1,
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188pub struct BwmTypeCode(u32);
189
190impl BwmTypeCode {
191 pub const fn from_raw(raw: u32) -> Self {
193 Self(raw)
194 }
195
196 pub const fn raw(self) -> u32 {
198 self.0
199 }
200
201 pub fn known(self) -> Option<BwmType> {
203 BwmType::try_from(self.0).ok()
204 }
205}
206
207impl From<BwmType> for BwmTypeCode {
208 fn from(value: BwmType) -> Self {
209 Self(u32::from(value))
210 }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq)]
215pub struct BwmVec3 {
216 pub x: f32,
218 pub y: f32,
220 pub z: f32,
222}
223
224impl BwmVec3 {
225 pub const fn new(x: f32, y: f32, z: f32) -> Self {
227 Self { x, y, z }
228 }
229}
230
231#[derive(Debug, Clone, PartialEq)]
233pub struct BwmFace {
234 pub vertex_indices: [u32; 3],
236 pub material_id: u32,
238 pub normal: BwmVec3,
240 pub planar_distance: f32,
242}
243
244#[derive(Debug, Clone, PartialEq)]
246pub struct BwmAabbNode {
247 pub bb_min: BwmVec3,
249 pub bb_max: BwmVec3,
251 pub face_index: u32,
253 pub unknown: u32,
255 pub split_axis: u32,
257 pub left_child: u32,
259 pub right_child: u32,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
265pub struct BwmAdjacency {
266 pub edge_refs: [i32; 3],
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct BwmEdge {
273 pub edge_index: i32,
275 pub transition: i32,
277}
278
279#[derive(Debug, Clone, PartialEq)]
281pub struct Bwm {
282 pub walkmesh_type: BwmTypeCode,
284 pub relative_hook1: BwmVec3,
286 pub relative_hook2: BwmVec3,
288 pub absolute_hook1: BwmVec3,
290 pub absolute_hook2: BwmVec3,
292 pub position: BwmVec3,
294 pub unknown: u32,
296 pub vertices: Vec<BwmVec3>,
298 pub faces: Vec<BwmFace>,
300 pub aabb_nodes: Vec<BwmAabbNode>,
302 pub adjacencies: Vec<BwmAdjacency>,
304 pub edges: Vec<BwmEdge>,
306 pub perimeters: Vec<u32>,
308}
309
310impl Default for Bwm {
311 fn default() -> Self {
312 Self {
313 walkmesh_type: BwmTypeCode::from(BwmType::AreaModel),
314 relative_hook1: BwmVec3::new(0.0, 0.0, 0.0),
315 relative_hook2: BwmVec3::new(0.0, 0.0, 0.0),
316 absolute_hook1: BwmVec3::new(0.0, 0.0, 0.0),
317 absolute_hook2: BwmVec3::new(0.0, 0.0, 0.0),
318 position: BwmVec3::new(0.0, 0.0, 0.0),
319 unknown: 0,
320 vertices: Vec::new(),
321 faces: Vec::new(),
322 aabb_nodes: Vec::new(),
323 adjacencies: Vec::new(),
324 edges: Vec::new(),
325 perimeters: Vec::new(),
326 }
327 }
328}
329
330impl Bwm {
331 pub fn new() -> Self {
333 Self::default()
334 }
335}
336
337impl DecodeBinary for Bwm {
338 type Error = BwmBinaryError;
339
340 fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
341 read_bwm_from_bytes(bytes)
342 }
343}
344
345impl EncodeBinary for Bwm {
346 type Error = BwmBinaryError;
347
348 fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
349 write_bwm_to_vec(self)
350 }
351}
352
353#[derive(Debug, Error)]
355pub enum BwmBinaryError {
356 #[error(transparent)]
358 Io(#[from] std::io::Error),
359 #[error("invalid BWM magic: {0:?}")]
361 InvalidMagic([u8; 4]),
362 #[error("invalid BWM version: {0:?}")]
364 InvalidVersion([u8; 4]),
365 #[error("invalid BWM header: {0}")]
367 InvalidHeader(String),
368 #[error("invalid BWM data: {0}")]
370 InvalidData(String),
371 #[error("value overflow while writing field `{0}`")]
373 ValueOverflow(&'static str),
374}
375
376impl From<binary::BinaryLayoutError> for BwmBinaryError {
377 fn from(error: binary::BinaryLayoutError) -> Self {
378 Self::InvalidHeader(error.to_string())
379 }
380}
381
382pub(super) fn checked_mul(
383 lhs: usize,
384 rhs: usize,
385 field: &'static str,
386) -> Result<usize, BwmBinaryError> {
387 lhs.checked_mul(rhs)
388 .ok_or(BwmBinaryError::ValueOverflow(field))
389}
390
391pub(super) fn checked_add(
392 lhs: usize,
393 rhs: usize,
394 field: &'static str,
395) -> Result<usize, BwmBinaryError> {
396 lhs.checked_add(rhs)
397 .ok_or(BwmBinaryError::ValueOverflow(field))
398}
399
400pub(super) fn usize_to_u32(value: usize, field: &'static str) -> Result<u32, BwmBinaryError> {
401 u32::try_from(value).map_err(|_| BwmBinaryError::ValueOverflow(field))
402}