1use std::collections::HashMap;
2use std::io::{Read, Seek, SeekFrom};
3use tracing::{debug, trace, warn};
4
5use crate::chunk::{Chunk, ChunkHeader};
6use crate::error::{Result, WmoError};
7use crate::types::{BoundingBox, ChunkId, Color, Vec3};
8use crate::version::{WmoFeature, WmoVersion};
9use crate::wmo_group_types::WmoGroupFlags;
10use crate::wmo_types::*;
11
12#[allow(dead_code)]
14trait ReadLittleEndian: Read {
15 fn read_u8(&mut self) -> Result<u8> {
16 let mut buf = [0u8; 1];
17 self.read_exact(&mut buf)?;
18 Ok(buf[0])
19 }
20
21 fn read_u16_le(&mut self) -> Result<u16> {
22 let mut buf = [0u8; 2];
23 self.read_exact(&mut buf)?;
24 Ok(u16::from_le_bytes(buf))
25 }
26
27 fn read_u32_le(&mut self) -> Result<u32> {
28 let mut buf = [0u8; 4];
29 self.read_exact(&mut buf)?;
30 Ok(u32::from_le_bytes(buf))
31 }
32
33 fn read_i16_le(&mut self) -> Result<i16> {
34 let mut buf = [0u8; 2];
35 self.read_exact(&mut buf)?;
36 Ok(i16::from_le_bytes(buf))
37 }
38
39 fn read_i32_le(&mut self) -> Result<i32> {
40 let mut buf = [0u8; 4];
41 self.read_exact(&mut buf)?;
42 Ok(i32::from_le_bytes(buf))
43 }
44
45 fn read_f32_le(&mut self) -> Result<f32> {
46 let mut buf = [0u8; 4];
47 self.read_exact(&mut buf)?;
48 Ok(f32::from_le_bytes(buf))
49 }
50}
51
52impl<R: Read> ReadLittleEndian for R {}
53
54pub mod chunks {
56 use crate::types::ChunkId;
57
58 pub const MVER: ChunkId = ChunkId::from_str("MVER");
60 pub const MOHD: ChunkId = ChunkId::from_str("MOHD");
61 pub const MOTX: ChunkId = ChunkId::from_str("MOTX");
62 pub const MOMT: ChunkId = ChunkId::from_str("MOMT");
63 pub const MOGN: ChunkId = ChunkId::from_str("MOGN");
64 pub const MOGI: ChunkId = ChunkId::from_str("MOGI");
65 pub const MOSB: ChunkId = ChunkId::from_str("MOSB");
66 pub const MOPV: ChunkId = ChunkId::from_str("MOPV");
67 pub const MOPT: ChunkId = ChunkId::from_str("MOPT");
68 pub const MOPR: ChunkId = ChunkId::from_str("MOPR");
69 pub const MOVV: ChunkId = ChunkId::from_str("MOVV");
70 pub const MOVB: ChunkId = ChunkId::from_str("MOVB");
71 pub const MOLT: ChunkId = ChunkId::from_str("MOLT");
72 pub const MODS: ChunkId = ChunkId::from_str("MODS");
73 pub const MODN: ChunkId = ChunkId::from_str("MODN");
74 pub const MODD: ChunkId = ChunkId::from_str("MODD");
75 pub const MFOG: ChunkId = ChunkId::from_str("MFOG");
76 pub const MCVP: ChunkId = ChunkId::from_str("MCVP");
77
78 pub const MOGP: ChunkId = ChunkId::from_str("MOGP");
80 pub const MOPY: ChunkId = ChunkId::from_str("MOPY");
81 pub const MOVI: ChunkId = ChunkId::from_str("MOVI");
82 pub const MOVT: ChunkId = ChunkId::from_str("MOVT");
83 pub const MONR: ChunkId = ChunkId::from_str("MONR");
84 pub const MOTV: ChunkId = ChunkId::from_str("MOTV");
85 pub const MOBA: ChunkId = ChunkId::from_str("MOBA");
86 pub const MOLR: ChunkId = ChunkId::from_str("MOLR");
87 pub const MODR: ChunkId = ChunkId::from_str("MODR");
88 pub const MOBN: ChunkId = ChunkId::from_str("MOBN");
89 pub const MOBR: ChunkId = ChunkId::from_str("MOBR");
90 pub const MOCV: ChunkId = ChunkId::from_str("MOCV");
91 pub const MLIQ: ChunkId = ChunkId::from_str("MLIQ");
92 pub const MORI: ChunkId = ChunkId::from_str("MORI");
93 pub const MORB: ChunkId = ChunkId::from_str("MORB");
94}
95
96pub struct WmoParser;
98
99impl Default for WmoParser {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105impl WmoParser {
106 pub fn new() -> Self {
108 Self
109 }
110
111 pub fn parse_root<R: Read + Seek>(&self, reader: &mut R) -> Result<WmoRoot> {
113 let chunks = self.read_chunks(reader)?;
115
116 let version = self.parse_version(&chunks, reader)?;
118 debug!("WMO version: {:?}", version);
119
120 let header = self.parse_header(&chunks, reader, version)?;
122 debug!("WMO header: {:?}", header);
123
124 let textures = self.parse_textures(&chunks, reader)?;
126 debug!("Found {} textures", textures.len());
127
128 let materials = self.parse_materials(&chunks, reader, version, header.n_materials)?;
130 debug!("Found {} materials", materials.len());
131
132 let groups = self.parse_group_info(&chunks, reader, version, header.n_groups)?;
134 debug!("Found {} groups", groups.len());
135
136 let portals = self.parse_portals(&chunks, reader, header.n_portals)?;
138 debug!("Found {} portals", portals.len());
139
140 let portal_references = self.parse_portal_references(&chunks, reader)?;
142 debug!("Found {} portal references", portal_references.len());
143
144 let visible_block_lists = self.parse_visible_block_lists(&chunks, reader)?;
146 debug!("Found {} visible block lists", visible_block_lists.len());
147
148 let lights = self.parse_lights(&chunks, reader, version, header.n_lights)?;
150 debug!("Found {} lights", lights.len());
151
152 let doodad_names = self.parse_doodad_names(&chunks, reader)?;
154 debug!("Found {} doodad names", doodad_names.len());
155
156 let doodad_defs = self.parse_doodad_defs(&chunks, reader, version, header.n_doodad_defs)?;
158 debug!("Found {} doodad definitions", doodad_defs.len());
159
160 let doodad_sets =
162 self.parse_doodad_sets(&chunks, reader, doodad_names, header.n_doodad_sets)?;
163 debug!("Found {} doodad sets", doodad_sets.len());
164
165 let skybox = self.parse_skybox(&chunks, reader, version, &header)?;
167 debug!("Skybox: {:?}", skybox);
168
169 let convex_volume_planes = self.parse_convex_volume_planes(&chunks, reader, version)?;
171 if let Some(ref cvp) = convex_volume_planes {
172 debug!("Found {} convex volume planes", cvp.planes.len());
173 }
174
175 let bounding_box = self.calculate_global_bounding_box(&groups);
177
178 Ok(WmoRoot {
179 version,
180 materials,
181 groups,
182 portals,
183 portal_references,
184 visible_block_lists,
185 lights,
186 doodad_defs,
187 doodad_sets,
188 bounding_box,
189 textures,
190 header,
191 skybox,
192 convex_volume_planes,
193 })
194 }
195
196 fn read_chunks<R: Read + Seek>(&self, reader: &mut R) -> Result<HashMap<ChunkId, Chunk>> {
198 let mut chunks = HashMap::new();
199 let start_pos = reader.stream_position()?;
200 reader.seek(SeekFrom::Start(start_pos))?;
201
202 loop {
204 match ChunkHeader::read(reader) {
205 Ok(header) => {
206 trace!("Found chunk: {}, size: {}", header.id, header.size);
207 let data_pos = reader.stream_position()?;
208
209 chunks.insert(
210 header.id,
211 Chunk {
212 header,
213 data_position: data_pos,
214 },
215 );
216
217 reader.seek(SeekFrom::Current(header.size as i64))?;
218 }
219 Err(WmoError::UnexpectedEof) => {
220 break;
222 }
223 Err(e) => return Err(e),
224 }
225 }
226
227 reader.seek(SeekFrom::Start(start_pos))?;
229
230 Ok(chunks)
231 }
232
233 fn parse_version<R: Read + Seek>(
235 &self,
236 chunks: &HashMap<ChunkId, Chunk>,
237 reader: &mut R,
238 ) -> Result<WmoVersion> {
239 let version_chunk = chunks
240 .get(&chunks::MVER)
241 .ok_or_else(|| WmoError::MissingRequiredChunk("MVER".to_string()))?;
242
243 version_chunk.seek_to_data(reader)?;
244 let raw_version = reader.read_u32_le()?;
245
246 WmoVersion::from_raw(raw_version).ok_or(WmoError::InvalidVersion(raw_version))
247 }
248
249 fn parse_header<R: Read + Seek>(
251 &self,
252 chunks: &HashMap<ChunkId, Chunk>,
253 reader: &mut R,
254 _version: WmoVersion,
255 ) -> Result<WmoHeader> {
256 let header_chunk = chunks
257 .get(&chunks::MOHD)
258 .ok_or_else(|| WmoError::MissingRequiredChunk("MOHD".to_string()))?;
259
260 header_chunk.seek_to_data(reader)?;
261
262 let n_materials = reader.read_u32_le()?;
264 let n_groups = reader.read_u32_le()?;
265 let n_portals = reader.read_u32_le()?;
266 let n_lights = reader.read_u32_le()?;
267 let n_doodad_names = reader.read_u32_le()?;
268 let n_doodad_defs = reader.read_u32_le()?;
269 let n_doodad_sets = reader.read_u32_le()?;
270 let color_bytes = reader.read_u32_le()?;
271 let flags = WmoFlags::from_bits_truncate(reader.read_u32_le()?);
272
273 reader.seek(SeekFrom::Current(8))?; let ambient_color = Color {
278 r: ((color_bytes >> 16) & 0xFF) as u8,
279 g: ((color_bytes >> 8) & 0xFF) as u8,
280 b: (color_bytes & 0xFF) as u8,
281 a: ((color_bytes >> 24) & 0xFF) as u8,
282 };
283
284 Ok(WmoHeader {
285 n_materials,
286 n_groups,
287 n_portals,
288 n_lights,
289 n_doodad_names,
290 n_doodad_defs,
291 n_doodad_sets,
292 flags,
293 ambient_color,
294 })
295 }
296
297 fn parse_textures<R: Read + Seek>(
299 &self,
300 chunks: &HashMap<ChunkId, Chunk>,
301 reader: &mut R,
302 ) -> Result<Vec<String>> {
303 let motx_chunk = match chunks.get(&chunks::MOTX) {
304 Some(chunk) => chunk,
305 None => return Ok(Vec::new()), };
307
308 let motx_data = motx_chunk.read_data(reader)?;
309 let mut textures = Vec::new();
310
311 let mut current_string = String::new();
313
314 for &byte in &motx_data {
315 if byte == 0 {
316 if !current_string.is_empty() {
318 textures.push(current_string);
319 current_string = String::new();
320 }
321 } else {
322 current_string.push(byte as char);
324 }
325 }
326
327 Ok(textures)
328 }
329
330 fn parse_materials<R: Read + Seek>(
332 &self,
333 chunks: &HashMap<ChunkId, Chunk>,
334 reader: &mut R,
335 version: WmoVersion,
336 n_materials: u32,
337 ) -> Result<Vec<WmoMaterial>> {
338 let momt_chunk = match chunks.get(&chunks::MOMT) {
339 Some(chunk) => chunk,
340 None => return Ok(Vec::new()), };
342
343 momt_chunk.seek_to_data(reader)?;
344 let mut materials = Vec::with_capacity(n_materials as usize);
345
346 let material_size = if version >= WmoVersion::Mop { 64 } else { 40 };
348
349 for _ in 0..n_materials {
350 let flags = WmoMaterialFlags::from_bits_truncate(reader.read_u32_le()?);
351 let shader = reader.read_u32_le()?;
352 let blend_mode = reader.read_u32_le()?;
353 let texture1 = reader.read_u32_le()?;
354
355 let emissive_color = Color {
356 r: reader.read_u8()?,
357 g: reader.read_u8()?,
358 b: reader.read_u8()?,
359 a: reader.read_u8()?,
360 };
361
362 let sidn_color = Color {
363 r: reader.read_u8()?,
364 g: reader.read_u8()?,
365 b: reader.read_u8()?,
366 a: reader.read_u8()?,
367 };
368
369 let framebuffer_blend = Color {
370 r: reader.read_u8()?,
371 g: reader.read_u8()?,
372 b: reader.read_u8()?,
373 a: reader.read_u8()?,
374 };
375
376 let texture2 = reader.read_u32_le()?;
377
378 let diffuse_color = Color {
379 r: reader.read_u8()?,
380 g: reader.read_u8()?,
381 b: reader.read_u8()?,
382 a: reader.read_u8()?,
383 };
384
385 let ground_type = reader.read_u32_le()?;
386
387 let remaining_size = material_size - 40;
389 if remaining_size > 0 {
390 reader.seek(SeekFrom::Current(remaining_size as i64))?;
391 }
392
393 materials.push(WmoMaterial {
394 flags,
395 shader,
396 blend_mode,
397 texture1,
398 emissive_color,
399 sidn_color,
400 framebuffer_blend,
401 texture2,
402 diffuse_color,
403 ground_type,
404 });
405 }
406
407 Ok(materials)
408 }
409
410 fn parse_group_info<R: Read + Seek>(
412 &self,
413 chunks: &HashMap<ChunkId, Chunk>,
414 reader: &mut R,
415 _version: WmoVersion,
416 n_groups: u32,
417 ) -> Result<Vec<WmoGroupInfo>> {
418 let mogn_chunk = match chunks.get(&chunks::MOGN) {
420 Some(chunk) => chunk,
421 None => return Ok(Vec::new()), };
423
424 let group_names_data = mogn_chunk.read_data(reader)?;
425
426 let mogi_chunk = match chunks.get(&chunks::MOGI) {
428 Some(chunk) => chunk,
429 None => return Ok(Vec::new()), };
431
432 mogi_chunk.seek_to_data(reader)?;
433 let mut groups = Vec::with_capacity(n_groups as usize);
434
435 for i in 0..n_groups {
436 let flags = WmoGroupFlags::from_bits_truncate(reader.read_u32_le()?);
437
438 let min_x = reader.read_f32_le()?;
439 let min_y = reader.read_f32_le()?;
440 let min_z = reader.read_f32_le()?;
441 let max_x = reader.read_f32_le()?;
442 let max_y = reader.read_f32_le()?;
443 let max_z = reader.read_f32_le()?;
444
445 let name_offset = reader.read_u32_le()?;
446
447 let name = if name_offset < group_names_data.len() as u32 {
449 let name = self.get_string_at_offset(&group_names_data, name_offset as usize);
450 if name.is_empty() {
451 format!("Group_{i}")
452 } else {
453 name
454 }
455 } else {
456 format!("Group_{i}")
457 };
458
459 groups.push(WmoGroupInfo {
460 flags,
461 bounding_box: BoundingBox {
462 min: Vec3 {
463 x: min_x,
464 y: min_y,
465 z: min_z,
466 },
467 max: Vec3 {
468 x: max_x,
469 y: max_y,
470 z: max_z,
471 },
472 },
473 name,
474 });
475 }
476
477 Ok(groups)
478 }
479
480 fn parse_portals<R: Read + Seek>(
482 &self,
483 chunks: &HashMap<ChunkId, Chunk>,
484 reader: &mut R,
485 n_portals: u32,
486 ) -> Result<Vec<WmoPortal>> {
487 let mopv_chunk = match chunks.get(&chunks::MOPV) {
489 Some(chunk) => chunk,
490 None => return Ok(Vec::new()), };
492
493 let mopv_data = mopv_chunk.read_data(reader)?;
494 let n_vertices = mopv_data.len() / 12; let mut portal_vertices = Vec::with_capacity(n_vertices);
496
497 for i in 0..n_vertices {
498 let offset = i * 12;
499
500 let x = f32::from_le_bytes([
502 mopv_data[offset],
503 mopv_data[offset + 1],
504 mopv_data[offset + 2],
505 mopv_data[offset + 3],
506 ]);
507
508 let y = f32::from_le_bytes([
509 mopv_data[offset + 4],
510 mopv_data[offset + 5],
511 mopv_data[offset + 6],
512 mopv_data[offset + 7],
513 ]);
514
515 let z = f32::from_le_bytes([
516 mopv_data[offset + 8],
517 mopv_data[offset + 9],
518 mopv_data[offset + 10],
519 mopv_data[offset + 11],
520 ]);
521
522 portal_vertices.push(Vec3 { x, y, z });
523 }
524
525 let mopt_chunk = match chunks.get(&chunks::MOPT) {
527 Some(chunk) => chunk,
528 None => return Ok(Vec::new()), };
530
531 mopt_chunk.seek_to_data(reader)?;
532 let mut portals = Vec::with_capacity(n_portals as usize);
533
534 for _ in 0..n_portals {
535 let vertex_index = reader.read_u16_le()? as usize;
536 let n_vertices = reader.read_u16_le()? as usize;
537
538 let normal_x = reader.read_f32_le()?;
539 let normal_y = reader.read_f32_le()?;
540 let normal_z = reader.read_f32_le()?;
541
542 reader.seek(SeekFrom::Current(4))?;
544
545 let mut vertices = Vec::with_capacity(n_vertices);
547 for i in 0..n_vertices {
548 let vertex_idx = vertex_index + i;
549 if vertex_idx < portal_vertices.len() {
550 vertices.push(portal_vertices[vertex_idx]);
551 } else {
552 warn!("Portal vertex index out of bounds: {}", vertex_idx);
553 }
554 }
555
556 portals.push(WmoPortal {
557 vertices,
558 normal: Vec3 {
559 x: normal_x,
560 y: normal_y,
561 z: normal_z,
562 },
563 });
564 }
565
566 Ok(portals)
567 }
568
569 fn parse_portal_references<R: Read + Seek>(
571 &self,
572 chunks: &HashMap<ChunkId, Chunk>,
573 reader: &mut R,
574 ) -> Result<Vec<WmoPortalReference>> {
575 let mopr_chunk = match chunks.get(&chunks::MOPR) {
576 Some(chunk) => chunk,
577 None => return Ok(Vec::new()), };
579
580 let mopr_data = mopr_chunk.read_data(reader)?;
581 let n_refs = mopr_data.len() / 8; let mut refs = Vec::with_capacity(n_refs);
583
584 for i in 0..n_refs {
585 let offset = i * 8;
586
587 let portal_index = u16::from_le_bytes([mopr_data[offset], mopr_data[offset + 1]]);
588
589 let group_index = u16::from_le_bytes([mopr_data[offset + 2], mopr_data[offset + 3]]);
590
591 let side = u16::from_le_bytes([mopr_data[offset + 4], mopr_data[offset + 5]]);
592
593 refs.push(WmoPortalReference {
596 portal_index,
597 group_index,
598 side,
599 });
600 }
601
602 Ok(refs)
603 }
604
605 fn parse_visible_block_lists<R: Read + Seek>(
607 &self,
608 chunks: &HashMap<ChunkId, Chunk>,
609 reader: &mut R,
610 ) -> Result<Vec<Vec<u16>>> {
611 let movv_chunk = match chunks.get(&chunks::MOVV) {
613 Some(chunk) => chunk,
614 None => return Ok(Vec::new()), };
616
617 let movv_data = movv_chunk.read_data(reader)?;
618 let n_entries = movv_data.len() / 4; let mut offsets = Vec::with_capacity(n_entries);
620
621 for i in 0..n_entries {
622 let offset = u32::from_le_bytes([
623 movv_data[i * 4],
624 movv_data[i * 4 + 1],
625 movv_data[i * 4 + 2],
626 movv_data[i * 4 + 3],
627 ]);
628
629 offsets.push(offset);
630 }
631
632 let movb_chunk = match chunks.get(&chunks::MOVB) {
634 Some(chunk) => chunk,
635 None => return Ok(Vec::new()), };
637
638 let movb_data = movb_chunk.read_data(reader)?;
639 let mut visible_lists = Vec::with_capacity(offsets.len());
640
641 for &offset in &offsets {
642 let mut index = offset as usize;
643 let mut list = Vec::new();
644
645 while index + 1 < movb_data.len() {
647 let value = u16::from_le_bytes([movb_data[index], movb_data[index + 1]]);
648
649 if value == 0xFFFF {
650 break;
652 }
653
654 list.push(value);
655 index += 2;
656 }
657
658 visible_lists.push(list);
659 }
660
661 Ok(visible_lists)
662 }
663
664 fn parse_lights<R: Read + Seek>(
666 &self,
667 chunks: &HashMap<ChunkId, Chunk>,
668 reader: &mut R,
669 _version: WmoVersion,
670 n_lights: u32,
671 ) -> Result<Vec<WmoLight>> {
672 let molt_chunk = match chunks.get(&chunks::MOLT) {
673 Some(chunk) => chunk,
674 None => return Ok(Vec::new()), };
676
677 molt_chunk.seek_to_data(reader)?;
678 let mut lights = Vec::with_capacity(n_lights as usize);
679
680 for _ in 0..n_lights {
681 let light_type_raw = reader.read_u8()?;
682 let light_type = WmoLightType::from_raw(light_type_raw).ok_or_else(|| {
683 WmoError::InvalidFormat(format!("Invalid light type: {light_type_raw}"))
684 })?;
685
686 let use_attenuation = reader.read_u8()? != 0;
688 let _use_unknown1 = reader.read_u8()?; let _use_unknown2 = reader.read_u8()?; let color_bytes = reader.read_u32_le()?;
693 let color = Color {
694 b: (color_bytes & 0xFF) as u8,
695 g: ((color_bytes >> 8) & 0xFF) as u8,
696 r: ((color_bytes >> 16) & 0xFF) as u8,
697 a: ((color_bytes >> 24) & 0xFF) as u8,
698 };
699
700 let pos_x = reader.read_f32_le()?;
701 let pos_y = reader.read_f32_le()?;
702 let pos_z = reader.read_f32_le()?;
703
704 let position = Vec3 {
705 x: pos_x,
706 y: pos_y,
707 z: pos_z,
708 };
709
710 let intensity = reader.read_f32_le()?;
711
712 let attenuation_start = reader.read_f32_le()?;
713 let attenuation_end = reader.read_f32_le()?;
714
715 reader.seek(SeekFrom::Current(16))?;
718
719 let properties = match light_type {
721 WmoLightType::Spot => WmoLightProperties::Spot {
722 direction: Vec3 {
723 x: 0.0,
724 y: 0.0,
725 z: -1.0,
726 }, hotspot: 0.0,
728 falloff: 0.0,
729 },
730 WmoLightType::Directional => WmoLightProperties::Directional {
731 direction: Vec3 {
732 x: 0.0,
733 y: 0.0,
734 z: -1.0,
735 }, },
737 WmoLightType::Omni => WmoLightProperties::Omni,
738 WmoLightType::Ambient => WmoLightProperties::Ambient,
739 };
740
741 lights.push(WmoLight {
742 light_type,
743 position,
744 color,
745 intensity,
746 attenuation_start,
747 attenuation_end,
748 use_attenuation,
749 properties,
750 });
751 }
752
753 Ok(lights)
754 }
755
756 fn parse_doodad_names<R: Read + Seek>(
758 &self,
759 chunks: &HashMap<ChunkId, Chunk>,
760 reader: &mut R,
761 ) -> Result<Vec<String>> {
762 let modn_chunk = match chunks.get(&chunks::MODN) {
763 Some(chunk) => chunk,
764 None => return Ok(Vec::new()), };
766
767 let modn_data = modn_chunk.read_data(reader)?;
768 Ok(self.parse_string_list(&modn_data))
769 }
770
771 fn parse_doodad_defs<R: Read + Seek>(
773 &self,
774 chunks: &HashMap<ChunkId, Chunk>,
775 reader: &mut R,
776 _version: WmoVersion,
777 n_doodad_defs: u32,
778 ) -> Result<Vec<WmoDoodadDef>> {
779 let modd_chunk = match chunks.get(&chunks::MODD) {
780 Some(chunk) => chunk,
781 None => return Ok(Vec::new()), };
783
784 modd_chunk.seek_to_data(reader)?;
785
786 let actual_doodad_count = modd_chunk.header.size / 40;
789 if actual_doodad_count != n_doodad_defs {
790 warn!(
791 "MODD chunk size indicates {} doodads, but header says {}. Using chunk size.",
792 actual_doodad_count, n_doodad_defs
793 );
794 }
795
796 let mut doodads = Vec::with_capacity(actual_doodad_count as usize);
797
798 for _ in 0..actual_doodad_count {
799 let name_index_raw = reader.read_u32_le()?;
800 let name_offset = name_index_raw & 0x00FFFFFF;
802
803 let pos_x = reader.read_f32_le()?;
804 let pos_y = reader.read_f32_le()?;
805 let pos_z = reader.read_f32_le()?;
806
807 let quat_x = reader.read_f32_le()?;
808 let quat_y = reader.read_f32_le()?;
809 let quat_z = reader.read_f32_le()?;
810 let quat_w = reader.read_f32_le()?;
811
812 let scale = reader.read_f32_le()?;
813
814 let color_bytes = reader.read_u32_le()?;
815 let color = Color {
816 r: ((color_bytes >> 16) & 0xFF) as u8,
817 g: ((color_bytes >> 8) & 0xFF) as u8,
818 b: (color_bytes & 0xFF) as u8,
819 a: ((color_bytes >> 24) & 0xFF) as u8,
820 };
821
822 let set_index = 0; doodads.push(WmoDoodadDef {
827 name_offset,
828 position: Vec3 {
829 x: pos_x,
830 y: pos_y,
831 z: pos_z,
832 },
833 orientation: [quat_x, quat_y, quat_z, quat_w],
834 scale,
835 color,
836 set_index,
837 });
838 }
839
840 Ok(doodads)
841 }
842
843 fn parse_doodad_sets<R: Read + Seek>(
845 &self,
846 chunks: &HashMap<ChunkId, Chunk>,
847 reader: &mut R,
848 _doodad_names: Vec<String>,
849 n_doodad_sets: u32,
850 ) -> Result<Vec<WmoDoodadSet>> {
851 let mods_chunk = match chunks.get(&chunks::MODS) {
852 Some(chunk) => chunk,
853 None => return Ok(Vec::new()), };
855
856 mods_chunk.seek_to_data(reader)?;
857 let mut sets = Vec::with_capacity(n_doodad_sets as usize);
858
859 for _i in 0..n_doodad_sets {
860 let mut name_bytes = [0u8; 20];
862 reader.read_exact(&mut name_bytes)?;
863
864 let null_pos = name_bytes.iter().position(|&b| b == 0).unwrap_or(20);
866 let name = String::from_utf8_lossy(&name_bytes[0..null_pos]).to_string();
867
868 let start_doodad = reader.read_u32_le()?;
869 let n_doodads = reader.read_u32_le()?;
870
871 reader.seek(SeekFrom::Current(4))?;
873
874 sets.push(WmoDoodadSet {
875 name,
876 start_doodad,
877 n_doodads,
878 });
879 }
880
881 Ok(sets)
882 }
883
884 fn parse_skybox<R: Read + Seek>(
886 &self,
887 chunks: &HashMap<ChunkId, Chunk>,
888 reader: &mut R,
889 version: WmoVersion,
890 header: &WmoHeader,
891 ) -> Result<Option<String>> {
892 if !version.supports_feature(WmoFeature::SkyboxReferences) {
894 return Ok(None);
895 }
896
897 if !header.flags.contains(WmoFlags::HAS_SKYBOX) {
899 return Ok(None);
900 }
901
902 let mosb_chunk = match chunks.get(&chunks::MOSB) {
903 Some(chunk) => chunk,
904 None => return Ok(None), };
906
907 let mosb_data = mosb_chunk.read_data(reader)?;
908
909 let null_pos = mosb_data
911 .iter()
912 .position(|&b| b == 0)
913 .unwrap_or(mosb_data.len());
914 let skybox_path = String::from_utf8_lossy(&mosb_data[0..null_pos]).to_string();
915
916 if skybox_path.is_empty() {
917 Ok(None)
918 } else {
919 Ok(Some(skybox_path))
920 }
921 }
922
923 fn parse_convex_volume_planes<R: Read + Seek>(
925 &self,
926 chunks: &HashMap<ChunkId, Chunk>,
927 reader: &mut R,
928 version: WmoVersion,
929 ) -> Result<Option<WmoConvexVolumePlanes>> {
930 if !version.supports_feature(WmoFeature::ConvexVolumePlanes) {
932 return Ok(None);
933 }
934
935 let mcvp_chunk = match chunks.get(&chunks::MCVP) {
936 Some(chunk) => chunk,
937 None => return Ok(None), };
939
940 let mcvp_data = mcvp_chunk.read_data(reader)?;
941
942 const PLANE_SIZE: usize = 20;
944
945 if mcvp_data.len() % PLANE_SIZE != 0 {
946 warn!(
947 "MCVP chunk size {} is not a multiple of plane size {}",
948 mcvp_data.len(),
949 PLANE_SIZE
950 );
951 return Ok(None);
952 }
953
954 let plane_count = mcvp_data.len() / PLANE_SIZE;
955 let mut planes = Vec::with_capacity(plane_count);
956
957 let mut cursor = std::io::Cursor::new(&mcvp_data);
958
959 for _ in 0..plane_count {
960 let normal = Vec3 {
961 x: cursor.read_f32_le()?,
962 y: cursor.read_f32_le()?,
963 z: cursor.read_f32_le()?,
964 };
965 let distance = cursor.read_f32_le()?;
966 let flags = cursor.read_u32_le()?;
967
968 planes.push(WmoConvexVolumePlane {
969 normal,
970 distance,
971 flags,
972 });
973 }
974
975 Ok(Some(WmoConvexVolumePlanes { planes }))
976 }
977
978 fn parse_string_list(&self, buffer: &[u8]) -> Vec<String> {
980 let mut strings = Vec::new();
981 let mut start = 0;
982
983 for i in 0..buffer.len() {
984 if buffer[i] == 0 {
985 if i > start {
986 if let Ok(s) = std::str::from_utf8(&buffer[start..i]) {
987 strings.push(s.to_string());
988 }
989 }
990 start = i + 1;
991 }
992 }
993
994 strings
995 }
996
997 fn get_string_at_offset(&self, buffer: &[u8], offset: usize) -> String {
999 if offset >= buffer.len() {
1000 return String::new();
1001 }
1002
1003 let end = buffer[offset..]
1005 .iter()
1006 .position(|&b| b == 0)
1007 .map(|pos| offset + pos)
1008 .unwrap_or(buffer.len());
1009
1010 if let Ok(s) = std::str::from_utf8(&buffer[offset..end]) {
1011 s.to_string()
1012 } else {
1013 String::new()
1014 }
1015 }
1016
1017 fn calculate_global_bounding_box(&self, groups: &[WmoGroupInfo]) -> BoundingBox {
1019 if groups.is_empty() {
1020 return BoundingBox {
1021 min: Vec3 {
1022 x: 0.0,
1023 y: 0.0,
1024 z: 0.0,
1025 },
1026 max: Vec3 {
1027 x: 0.0,
1028 y: 0.0,
1029 z: 0.0,
1030 },
1031 };
1032 }
1033
1034 let mut min_x = f32::MAX;
1035 let mut min_y = f32::MAX;
1036 let mut min_z = f32::MAX;
1037 let mut max_x = f32::MIN;
1038 let mut max_y = f32::MIN;
1039 let mut max_z = f32::MIN;
1040
1041 for group in groups {
1042 min_x = min_x.min(group.bounding_box.min.x);
1043 min_y = min_y.min(group.bounding_box.min.y);
1044 min_z = min_z.min(group.bounding_box.min.z);
1045 max_x = max_x.max(group.bounding_box.max.x);
1046 max_y = max_y.max(group.bounding_box.max.y);
1047 max_z = max_z.max(group.bounding_box.max.z);
1048 }
1049
1050 BoundingBox {
1051 min: Vec3 {
1052 x: min_x,
1053 y: min_y,
1054 z: min_z,
1055 },
1056 max: Vec3 {
1057 x: max_x,
1058 y: max_y,
1059 z: max_z,
1060 },
1061 }
1062 }
1063}