Skip to main content

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