wow_wmo/
chunks.rs

1use std::collections::HashMap;
2
3use binrw::BinRead;
4
5/// MOMT - Materials chunk
6#[derive(Debug, Clone, BinRead)]
7#[br(little)]
8pub struct MomtEntry {
9    pub flags: u32,
10    pub shader: u32,
11    pub blend_mode: u32,
12    pub texture_1: u32,
13    pub emissive_color: [u8; 4],
14    pub frame_emissive_color: [u8; 4],
15    pub texture_2: u32,
16    pub diff_color: [u8; 4],
17    pub ground_type: u32,
18    pub texture_3: u32,
19    pub color_2: u32,
20    pub flags_2: u32,
21    #[br(count = 16)]
22    pub runtime_data: Vec<u8>,
23}
24
25impl MomtEntry {
26    pub fn get_texture1_index(&self, texture_offset_index_map: &HashMap<u32, u32>) -> u32 {
27        texture_offset_index_map
28            .get(&self.texture_1)
29            .copied()
30            .unwrap()
31    }
32
33    pub fn get_texture2_index(&self, texture_offset_index_map: &HashMap<u32, u32>) -> u32 {
34        texture_offset_index_map
35            .get(&self.texture_2)
36            .copied()
37            .unwrap()
38    }
39
40    pub fn get_texture3_index(&self, texture_offset_index_map: &HashMap<u32, u32>) -> u32 {
41        texture_offset_index_map
42            .get(&self.texture_3)
43            .copied()
44            .unwrap()
45    }
46}
47
48/// MOGN - Group names chunk
49#[derive(Debug, Clone)]
50pub struct Mogn {
51    pub names: Vec<String>,
52}
53
54impl Mogn {
55    pub fn parse(data: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
56        let mut names = Vec::new();
57        let mut start = 0;
58
59        for i in 0..data.len() {
60            if data[i] == 0 {
61                if i > start {
62                    let name = String::from_utf8(data[start..i].to_vec())?;
63                    names.push(name);
64                }
65                start = i + 1;
66            }
67        }
68
69        Ok(Self { names })
70    }
71}
72
73/// MOGI - Group information chunk
74#[derive(Debug, Clone, BinRead)]
75#[br(little)]
76pub struct MogiEntry {
77    pub flags: u32,
78    pub bounding_box_min: [f32; 3],
79    pub bounding_box_max: [f32; 3],
80    pub name_offset: i32,
81}
82
83/// MOLT - Lights chunk
84#[derive(Debug, Clone, BinRead)]
85#[br(little)]
86pub struct MoltEntry {
87    pub light_type: u8,
88    pub use_attenuation: u8,
89    pub padding: [u8; 2],
90    pub color: [u8; 4],
91    pub position: [f32; 3],
92    pub intensity: f32,
93    pub attenuation_start: f32,
94    pub attenuation_end: f32,
95}
96
97/// MODS - Doodad sets chunk
98#[derive(Debug, Clone, BinRead)]
99#[br(little)]
100pub struct ModsEntry {
101    pub name: [u8; 20],
102    pub start_index: u32,
103    pub count: u32,
104    pub padding: u32,
105}
106
107/// MODD - Doodad definitions chunk
108#[derive(Debug, Clone, BinRead)]
109#[br(little)]
110pub struct ModdEntry {
111    pub name_index_and_flags: u32, // nameIndex is bits 0-23, flags are bits 24-31
112    pub position: [f32; 3],        // (X, Z, -Y)
113    pub orientation: [f32; 4],     // Quaternion (X, Y, Z, W)
114    pub scale: f32,
115    pub color: [u8; 4], // (B, G, R, A)
116}
117
118impl ModdEntry {
119    /// Extract the name index (bits 0-23)
120    pub fn name_index(&self) -> u32 {
121        self.name_index_and_flags & 0x00FFFFFF
122    }
123
124    /// Check if accepts projected textures (bit 24)
125    pub fn accepts_proj_tex(&self) -> bool {
126        (self.name_index_and_flags & 0x01000000) != 0
127    }
128
129    /// Check if uses interior lighting (bit 25)
130    pub fn uses_interior_lighting(&self) -> bool {
131        (self.name_index_and_flags & 0x02000000) != 0
132    }
133}
134
135/// MFOG - Fog chunk
136#[derive(Debug, Clone, BinRead)]
137#[br(little)]
138pub struct MfogEntry {
139    pub flags: u32,
140    pub position: [f32; 3],
141    pub smaller_radius: f32,
142    pub larger_radius: f32,
143    pub fog_end: f32,
144    pub fog_start_multiplier: f32,
145    pub color_1: [u8; 4],
146    pub color_2: [u8; 4],
147}
148
149/// MCVP - Convex volume planes (Cataclysm+)
150#[derive(Debug, Clone, BinRead)]
151#[br(little)]
152pub struct McvpEntry {
153    pub plane: [f32; 4], // Ax + By + Cz + D = 0
154}
155
156// Group file chunks
157
158/// MOPY - Material info for triangles
159#[derive(Debug, Clone, BinRead)]
160#[br(little)]
161pub struct MopyEntry {
162    pub flags: u8,
163    pub material_id: u8,
164}
165
166/// MOVI - Vertex indices for triangles
167pub type MoviEntry = u16; // Each index is a u16
168
169/// MOVT - Vertex positions
170#[derive(Debug, Clone, BinRead)]
171#[br(little)]
172pub struct MovtEntry {
173    pub x: f32,
174    pub y: f32,
175    pub z: f32,
176}
177
178/// MONR - Normals
179#[derive(Debug, Clone, BinRead)]
180#[br(little)]
181pub struct MonrEntry {
182    pub x: f32,
183    pub y: f32,
184    pub z: f32,
185}
186
187/// MOTV - Texture coordinates
188#[derive(Debug, Clone, BinRead)]
189#[br(little)]
190pub struct MotvEntry {
191    pub u: f32,
192    pub v: f32,
193}
194
195/// MOBA - Render batches
196/// Structure for WoW versions ≥ Vanilla (our supported range: 1.12.1 to 5.4.8)
197#[derive(Debug, Clone, BinRead)]
198#[br(little)]
199pub struct MobaEntry {
200    /// Bounding box for culling (bx, by, bz)
201    pub bounding_box_min: [i16; 3],
202    /// Bounding box for culling (tx, ty, tz)
203    pub bounding_box_max: [i16; 3],
204    /// Index of the first face index used in MOVI
205    pub start_index: u32,
206    /// Number of MOVI indices used
207    pub count: u16,
208    /// Index of the first vertex used in MOVT
209    pub min_index: u16,
210    /// Index of the last vertex used (batch includes this one)
211    pub max_index: u16,
212    /// Batch flags
213    pub flags: u8,
214    /// Material index in MOMT
215    pub material_id: u8,
216}
217
218/// MOLR - Light references
219pub type MolrEntry = u16; // Light index reference
220
221/// MODR - Doodad references
222pub type ModrEntry = u16; // Doodad index reference
223
224/// MOBN - BSP tree nodes
225#[derive(Debug, Clone, BinRead)]
226#[br(little)]
227pub struct MobnEntry {
228    pub flags: u16,
229    pub neg_child: i16,
230    pub pos_child: i16,
231    pub n_faces: u16,
232    pub face_start: u32,
233    pub plane_distance: f32,
234}
235
236/// MOBR - BSP tree face indices
237pub type MobrEntry = u16; // Face index
238
239/// MOCV - Vertex colors
240#[derive(Debug, Clone, BinRead)]
241#[br(little)]
242pub struct MocvEntry {
243    pub b: u8,
244    pub g: u8,
245    pub r: u8,
246    pub a: u8,
247}
248
249/// MLIQ - Liquid data
250#[derive(Debug, Clone, BinRead)]
251#[br(little)]
252pub struct MliqHeader {
253    pub x_tiles: u32,
254    pub y_tiles: u32,
255    pub x_corner: f32,
256    pub y_corner: f32,
257    pub liquid_type: u32,
258    pub material_id: u32,
259}
260
261/// MLIQ vertex entry
262#[derive(Debug, Clone, BinRead)]
263#[br(little)]
264pub struct MliqVertex {
265    pub position: [f32; 3],
266    pub texture_coord: [f32; 2],
267}
268
269/// MOTX - Texture names (parsed as string list)
270#[derive(Debug, Clone)]
271pub struct Motx {
272    pub textures: Vec<String>,
273    pub texture_offset_index_map: HashMap<u32, u32>,
274}
275
276impl Motx {
277    pub fn parse(data: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
278        let mut textures = Vec::new();
279        let mut start = 0;
280        let mut texture_offset_index_map = HashMap::new();
281
282        for i in 0..data.len() {
283            if data[i] == 0 {
284                if i > start {
285                    let texture = String::from_utf8(data[start..i].to_vec())?;
286                    textures.push(texture);
287                    texture_offset_index_map.insert(start as u32, textures.len() as u32 - 1);
288                }
289                start = i + 1;
290            }
291        }
292
293        Ok(Self {
294            textures,
295            texture_offset_index_map,
296        })
297    }
298}
299
300/// MOSB - Skybox name
301#[derive(Debug, Clone)]
302pub struct Mosb {
303    pub skybox: Option<String>,
304}
305
306impl Mosb {
307    pub fn parse(data: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
308        if data.is_empty() {
309            return Ok(Self { skybox: None });
310        }
311
312        // Find null terminator
313        let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
314        if end > 0 {
315            let skybox = String::from_utf8(data[..end].to_vec())?;
316            Ok(Self {
317                skybox: Some(skybox),
318            })
319        } else {
320            Ok(Self { skybox: None })
321        }
322    }
323}
324
325/// MOPV - Portal vertices
326#[derive(Debug, Clone, BinRead)]
327#[br(little)]
328pub struct MopvEntry {
329    pub x: f32,
330    pub y: f32,
331    pub z: f32,
332}
333
334/// MOPT - Portal information
335#[derive(Debug, Clone, BinRead)]
336#[br(little)]
337pub struct MoptEntry {
338    pub start_vertex: u16,
339    pub n_vertices: u16,
340    pub normal: MopvEntry, // Reusing Vec3 structure
341    pub distance: f32,
342}
343
344/// MOPR - Portal references
345#[derive(Debug, Clone, BinRead)]
346#[br(little)]
347pub struct MoprEntry {
348    pub portal_index: u16,
349    pub group_index: u16,
350    pub side: i16,
351    pub padding: u16,
352}
353
354/// MOVV - Visible block vertices
355#[derive(Debug, Clone, BinRead)]
356#[br(little)]
357pub struct MovvEntry {
358    pub x: f32,
359    pub y: f32,
360    pub z: f32,
361}
362
363/// MOVB - Visible block list
364#[derive(Debug, Clone, BinRead)]
365#[br(little)]
366pub struct MovbEntry {
367    pub start_vertex: u16,
368    pub vertex_count: u16,
369}
370
371/// MODN - Doodad names (parsed as string list)
372#[derive(Debug, Clone)]
373pub struct Modn {
374    pub names: Vec<String>,
375}
376
377/// MOMO - Alpha version container chunk (version 14 only)
378/// Container chunk that wraps other chunks in early WoW versions
379#[derive(Debug, Clone)]
380pub struct MomoEntry {
381    // No additional data - acts as container for other chunks
382}
383
384/// MOM3 - New materials (WarWithin+)
385#[derive(Debug, Clone)]
386pub struct Mom3Entry {
387    // m3SI structure - defines new materials
388    // Structure details may vary, treated as opaque for now
389    pub data: Vec<u8>,
390}
391
392/// MOUV - UV transformations (Legion+)
393#[derive(Debug, Clone, BinRead)]
394#[br(little)]
395pub struct MouvEntry {
396    pub translation_speed: [[f32; 2]; 2], // 2 C2Vectors per material
397}
398
399/// MOPE - Portal extra information (WarWithin+)
400#[derive(Debug, Clone, BinRead)]
401#[br(little)]
402pub struct MopeEntry {
403    pub portal_index: u32, // index into MOPT
404    pub unk1: u32,
405    pub unk2: u32,
406    pub unk3: u32,
407}
408
409/// MOLV - Light extensions (Shadowlands+)
410#[derive(Debug, Clone, BinRead)]
411#[br(little)]
412pub struct MolvEntry {
413    pub directions: [[f32; 4]; 6], // 6 sets of C3Vector + float value
414    pub unknown: [u8; 3],
415    pub molt_index: u8,
416}
417
418/// MODI - Doodad file IDs (Battle for Azeroth+)
419pub type ModiEntry = u32; // Doodad ID, same count as SMOHeader.nDoodadNames
420
421/// MOGX - Query face start (Dragonflight+)
422pub type MogxEntry = u32; // Query face start index
423
424/// MPY2 - Extended material info (Dragonflight+)
425#[derive(Debug, Clone, BinRead)]
426#[br(little)]
427pub struct Mpy2Entry {
428    pub flags: u16,
429    pub material_id: u16,
430}
431
432/// MOVX - Extended vertex indices (Shadowlands+)
433/// Possible replacement for MOVI chunk allowing larger indices
434pub type MovxEntry = u32; // Extended vertex index (u32 instead of u16)
435
436/// MOQG - Query faces (Dragonflight+)
437pub type MoqgEntry = u32; // Ground type values
438
439/// GFID - Group file IDs (modern WoW versions)
440pub type GfidEntry = u32; // File ID for group files
441
442/// MORI - Triangle strip indices (optimized rendering)
443pub type MoriEntry = u16; // Triangle strip index
444
445/// MORB - Additional render batches (extended batching)
446#[derive(Debug, Clone, BinRead)]
447#[br(little)]
448pub struct MorbEntry {
449    pub start_index: u16,
450    pub index_count: u16,
451    pub min_index: u16,
452    pub max_index: u16,
453    pub flags: u8,
454    pub material_id: u8,
455}
456
457/// MOTA - Tangent array (normal mapping)
458#[derive(Debug, Clone, BinRead)]
459#[br(little)]
460pub struct MotaEntry {
461    pub tangent: [i16; 4], // Packed tangent vector (x, y, z, w)
462}
463
464impl MotaEntry {
465    /// Convert packed tangent to normalized float vector
466    pub fn to_float_tangent(&self) -> [f32; 4] {
467        [
468            self.tangent[0] as f32 / 32767.0,
469            self.tangent[1] as f32 / 32767.0,
470            self.tangent[2] as f32 / 32767.0,
471            self.tangent[3] as f32 / 32767.0,
472        ]
473    }
474}
475
476/// MOBS - Shadow batches (shadow rendering)
477/// Note: Based on real-world data analysis, index_count appears to use signed values
478#[derive(Debug, Clone, BinRead)]
479#[br(little)]
480pub struct MobsEntry {
481    pub start_index: u16,
482    pub index_count: i16, // Changed to signed based on data analysis
483    pub min_index: u16,
484    pub max_index: u16,
485    pub flags: u8,
486    pub material_id: u8,
487}
488
489impl Modn {
490    pub fn parse(data: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
491        let mut names = Vec::new();
492        let mut start = 0;
493
494        for i in 0..data.len() {
495            if data[i] == 0 {
496                if i > start {
497                    let name = String::from_utf8(data[start..i].to_vec())?;
498                    names.push(name);
499                }
500                start = i + 1;
501            }
502        }
503
504        Ok(Self { names })
505    }
506}