1use std::collections::HashMap;
2use std::io::{Read, Seek, SeekFrom};
3use tracing::{debug, trace};
4
5use crate::chunk::{Chunk, ChunkHeader};
6use crate::error::{Result, WmoError};
7use crate::parser::chunks;
8use crate::types::{BoundingBox, ChunkId, Color, Vec3};
9use crate::version::{WmoFeature, WmoVersion};
10use crate::wmo_group_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 struct WmoGroupParser;
56
57impl Default for WmoGroupParser {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63impl WmoGroupParser {
64 pub fn new() -> Self {
66 Self
67 }
68
69 pub fn parse_group<R: Read + Seek>(
71 &self,
72 reader: &mut R,
73 group_index: u32,
74 ) -> Result<WmoGroup> {
75 let chunks = self.read_chunks(reader)?;
77
78 let version = self.parse_version(&chunks, reader)?;
80 debug!("WMO version: {:?}", version);
81
82 let header = self.parse_group_header(&chunks, reader, version, group_index)?;
84 debug!("WMO group header: {:?}", header);
85
86 let materials = self.parse_materials(&chunks, reader)?;
88 debug!("Found {} materials", materials.len());
89
90 let vertices = self.parse_vertices(&chunks, reader)?;
92 debug!("Found {} vertices", vertices.len());
93
94 let normals = if header.flags.contains(WmoGroupFlags::HAS_NORMALS) {
96 self.parse_normals(&chunks, reader)?
97 } else {
98 Vec::new()
99 };
100 debug!("Found {} normals", normals.len());
101
102 let tex_coords = self.parse_texture_coords(&chunks, reader)?;
104 debug!("Found {} texture coordinates", tex_coords.len());
105
106 let batches = self.parse_batches(&chunks, reader)?;
108 debug!("Found {} batches", batches.len());
109
110 let indices = self.parse_indices(&chunks, reader)?;
112 debug!("Found {} indices", indices.len());
113
114 let vertex_colors = if header.flags.contains(WmoGroupFlags::HAS_VERTEX_COLORS) {
116 Some(self.parse_vertex_colors(&chunks, reader)?)
117 } else {
118 None
119 };
120 debug!("Vertex colors: {}", vertex_colors.is_some());
121
122 let bsp_nodes = self.parse_bsp_nodes(&chunks, reader)?;
124 debug!("BSP nodes: {}", bsp_nodes.is_some());
125
126 let liquid = if header.flags.contains(WmoGroupFlags::HAS_WATER) {
128 self.parse_liquid(&chunks, reader, version)?
129 } else {
130 None
131 };
132 debug!("Liquid data: {}", liquid.is_some());
133
134 let doodad_refs = if header.flags.contains(WmoGroupFlags::HAS_DOODADS) {
136 Some(self.parse_doodad_refs(&chunks, reader)?)
137 } else {
138 None
139 };
140 debug!("Doodad references: {}", doodad_refs.is_some());
141
142 Ok(WmoGroup {
143 header,
144 materials,
145 vertices,
146 normals,
147 tex_coords,
148 batches,
149 indices,
150 vertex_colors,
151 bsp_nodes,
152 liquid,
153 doodad_refs,
154 })
155 }
156
157 fn read_chunks<R: Read + Seek>(&self, reader: &mut R) -> Result<HashMap<ChunkId, Chunk>> {
159 let mut chunks = HashMap::new();
160 let start_pos = reader.stream_position()?;
161 reader.seek(SeekFrom::Start(start_pos))?;
162
163 loop {
165 match ChunkHeader::read(reader) {
166 Ok(header) => {
167 trace!("Found chunk: {}, size: {}", header.id, header.size);
168 let data_pos = reader.stream_position()?;
169
170 chunks.insert(
171 header.id,
172 Chunk {
173 header,
174 data_position: data_pos,
175 },
176 );
177
178 reader.seek(SeekFrom::Current(header.size as i64))?;
179 }
180 Err(WmoError::UnexpectedEof) => {
181 break;
183 }
184 Err(e) => return Err(e),
185 }
186 }
187
188 reader.seek(SeekFrom::Start(start_pos))?;
190
191 Ok(chunks)
192 }
193
194 fn parse_version<R: Read + Seek>(
196 &self,
197 chunks: &HashMap<ChunkId, Chunk>,
198 reader: &mut R,
199 ) -> Result<WmoVersion> {
200 let version_chunk = chunks
201 .get(&chunks::MVER)
202 .ok_or_else(|| WmoError::MissingRequiredChunk("MVER".to_string()))?;
203
204 version_chunk.seek_to_data(reader)?;
205 let raw_version = reader.read_u32_le()?;
206
207 WmoVersion::from_raw(raw_version).ok_or(WmoError::InvalidVersion(raw_version))
208 }
209
210 fn parse_group_header<R: Read + Seek>(
212 &self,
213 chunks: &HashMap<ChunkId, Chunk>,
214 reader: &mut R,
215 _version: WmoVersion,
216 group_index: u32,
217 ) -> Result<WmoGroupHeader> {
218 let mogp_chunk = chunks
219 .get(&chunks::MOGP)
220 .ok_or_else(|| WmoError::MissingRequiredChunk("MOGP".to_string()))?;
221
222 mogp_chunk.seek_to_data(reader)?;
223
224 let name_offset = reader.read_u32_le()?;
226
227 let flags = WmoGroupFlags::from_bits_truncate(reader.read_u32_le()?);
229
230 let min_x = reader.read_f32_le()?;
232 let min_y = reader.read_f32_le()?;
233 let min_z = reader.read_f32_le()?;
234
235 let max_x = reader.read_f32_le()?;
236 let max_y = reader.read_f32_le()?;
237 let max_z = reader.read_f32_le()?;
238
239 reader.seek(SeekFrom::Current(8))?; Ok(WmoGroupHeader {
243 flags,
244 bounding_box: BoundingBox {
245 min: Vec3 {
246 x: min_x,
247 y: min_y,
248 z: min_z,
249 },
250 max: Vec3 {
251 x: max_x,
252 y: max_y,
253 z: max_z,
254 },
255 },
256 name_offset,
257 group_index,
258 })
259 }
260
261 fn parse_materials<R: Read + Seek>(
263 &self,
264 chunks: &HashMap<ChunkId, Chunk>,
265 reader: &mut R,
266 ) -> Result<Vec<u16>> {
267 let moba_chunk = match chunks.get(&chunks::MOBA) {
268 Some(chunk) => chunk,
269 None => return Ok(Vec::new()), };
271
272 let batch_data = moba_chunk.read_data(reader)?;
273 let mut materials = Vec::new();
274
275 let batch_count = batch_data.len() / 24;
277
278 for i in 0..batch_count {
279 let offset = i * 24 + 2; let material_id = u16::from_le_bytes([batch_data[offset], batch_data[offset + 1]]);
282
283 if !materials.contains(&material_id) {
284 materials.push(material_id);
285 }
286 }
287
288 Ok(materials)
289 }
290
291 fn parse_vertices<R: Read + Seek>(
293 &self,
294 chunks: &HashMap<ChunkId, Chunk>,
295 reader: &mut R,
296 ) -> Result<Vec<Vec3>> {
297 let movt_chunk = match chunks.get(&chunks::MOVT) {
298 Some(chunk) => chunk,
299 None => return Ok(Vec::new()), };
301
302 let movt_data = movt_chunk.read_data(reader)?;
303 let vertex_count = movt_data.len() / 12; let mut vertices = Vec::with_capacity(vertex_count);
305
306 for i in 0..vertex_count {
307 let offset = i * 12;
308
309 let x = f32::from_le_bytes([
310 movt_data[offset],
311 movt_data[offset + 1],
312 movt_data[offset + 2],
313 movt_data[offset + 3],
314 ]);
315
316 let y = f32::from_le_bytes([
317 movt_data[offset + 4],
318 movt_data[offset + 5],
319 movt_data[offset + 6],
320 movt_data[offset + 7],
321 ]);
322
323 let z = f32::from_le_bytes([
324 movt_data[offset + 8],
325 movt_data[offset + 9],
326 movt_data[offset + 10],
327 movt_data[offset + 11],
328 ]);
329
330 vertices.push(Vec3 { x, y, z });
331 }
332
333 Ok(vertices)
334 }
335
336 fn parse_normals<R: Read + Seek>(
338 &self,
339 chunks: &HashMap<ChunkId, Chunk>,
340 reader: &mut R,
341 ) -> Result<Vec<Vec3>> {
342 let monr_chunk = match chunks.get(&chunks::MONR) {
343 Some(chunk) => chunk,
344 None => return Ok(Vec::new()), };
346
347 let monr_data = monr_chunk.read_data(reader)?;
348 let normal_count = monr_data.len() / 12; let mut normals = Vec::with_capacity(normal_count);
350
351 for i in 0..normal_count {
352 let offset = i * 12;
353
354 let x = f32::from_le_bytes([
355 monr_data[offset],
356 monr_data[offset + 1],
357 monr_data[offset + 2],
358 monr_data[offset + 3],
359 ]);
360
361 let y = f32::from_le_bytes([
362 monr_data[offset + 4],
363 monr_data[offset + 5],
364 monr_data[offset + 6],
365 monr_data[offset + 7],
366 ]);
367
368 let z = f32::from_le_bytes([
369 monr_data[offset + 8],
370 monr_data[offset + 9],
371 monr_data[offset + 10],
372 monr_data[offset + 11],
373 ]);
374
375 normals.push(Vec3 { x, y, z });
376 }
377
378 Ok(normals)
379 }
380
381 fn parse_texture_coords<R: Read + Seek>(
383 &self,
384 chunks: &HashMap<ChunkId, Chunk>,
385 reader: &mut R,
386 ) -> Result<Vec<TexCoord>> {
387 let motv_chunk = match chunks.get(&chunks::MOTV) {
388 Some(chunk) => chunk,
389 None => return Ok(Vec::new()), };
391
392 let motv_data = motv_chunk.read_data(reader)?;
393 let tex_coord_count = motv_data.len() / 8; let mut tex_coords = Vec::with_capacity(tex_coord_count);
395
396 for i in 0..tex_coord_count {
397 let offset = i * 8;
398
399 let u = f32::from_le_bytes([
400 motv_data[offset],
401 motv_data[offset + 1],
402 motv_data[offset + 2],
403 motv_data[offset + 3],
404 ]);
405
406 let v = f32::from_le_bytes([
407 motv_data[offset + 4],
408 motv_data[offset + 5],
409 motv_data[offset + 6],
410 motv_data[offset + 7],
411 ]);
412
413 tex_coords.push(TexCoord { u, v });
414 }
415
416 Ok(tex_coords)
417 }
418
419 fn parse_batches<R: Read + Seek>(
421 &self,
422 chunks: &HashMap<ChunkId, Chunk>,
423 reader: &mut R,
424 ) -> Result<Vec<WmoBatch>> {
425 let moba_chunk = match chunks.get(&chunks::MOBA) {
426 Some(chunk) => chunk,
427 None => return Ok(Vec::new()), };
429
430 let moba_data = moba_chunk.read_data(reader)?;
431 let batch_count = moba_data.len() / 24; let mut batches = Vec::with_capacity(batch_count);
433
434 for i in 0..batch_count {
435 let offset = i * 24;
436
437 let flags = moba_data[offset];
438
439 let material_id = u16::from_le_bytes([moba_data[offset + 2], moba_data[offset + 3]]);
440
441 let start_index = u32::from_le_bytes([
442 moba_data[offset + 4],
443 moba_data[offset + 5],
444 moba_data[offset + 6],
445 moba_data[offset + 7],
446 ]);
447
448 let count = u16::from_le_bytes([moba_data[offset + 8], moba_data[offset + 9]]);
449
450 let start_vertex = u16::from_le_bytes([moba_data[offset + 10], moba_data[offset + 11]]);
451
452 let end_vertex = u16::from_le_bytes([moba_data[offset + 12], moba_data[offset + 13]]);
453
454 batches.push(WmoBatch {
457 flags,
458 material_id,
459 start_index,
460 count,
461 start_vertex,
462 end_vertex,
463 });
464 }
465
466 Ok(batches)
467 }
468
469 fn parse_indices<R: Read + Seek>(
471 &self,
472 chunks: &HashMap<ChunkId, Chunk>,
473 reader: &mut R,
474 ) -> Result<Vec<u16>> {
475 let movi_chunk = match chunks.get(&chunks::MOVI) {
476 Some(chunk) => chunk,
477 None => return Ok(Vec::new()), };
479
480 let movi_data = movi_chunk.read_data(reader)?;
481 let index_count = movi_data.len() / 2; let mut indices = Vec::with_capacity(index_count);
483
484 for i in 0..index_count {
485 let offset = i * 2;
486
487 let index = u16::from_le_bytes([movi_data[offset], movi_data[offset + 1]]);
488
489 indices.push(index);
490 }
491
492 Ok(indices)
493 }
494
495 fn parse_vertex_colors<R: Read + Seek>(
497 &self,
498 chunks: &HashMap<ChunkId, Chunk>,
499 reader: &mut R,
500 ) -> Result<Vec<Color>> {
501 let mocv_chunk = match chunks.get(&chunks::MOCV) {
502 Some(chunk) => chunk,
503 None => return Ok(Vec::new()), };
505
506 let mocv_data = mocv_chunk.read_data(reader)?;
507 let color_count = mocv_data.len() / 4; let mut colors = Vec::with_capacity(color_count);
509
510 for i in 0..color_count {
511 let offset = i * 4;
512
513 let color = Color {
514 b: mocv_data[offset],
515 g: mocv_data[offset + 1],
516 r: mocv_data[offset + 2],
517 a: mocv_data[offset + 3],
518 };
519
520 colors.push(color);
521 }
522
523 Ok(colors)
524 }
525
526 fn parse_bsp_nodes<R: Read + Seek>(
528 &self,
529 chunks: &HashMap<ChunkId, Chunk>,
530 reader: &mut R,
531 ) -> Result<Option<Vec<WmoBspNode>>> {
532 let mobn_chunk = match chunks.get(&chunks::MOBN) {
533 Some(chunk) => chunk,
534 None => return Ok(None), };
536
537 let mobn_data = mobn_chunk.read_data(reader)?;
538 let node_count = mobn_data.len() / 16; let mut nodes = Vec::with_capacity(node_count);
540
541 for i in 0..node_count {
542 let offset = i * 16;
543
544 let plane_normal_x = f32::from_le_bytes([
545 mobn_data[offset],
546 mobn_data[offset + 1],
547 mobn_data[offset + 2],
548 mobn_data[offset + 3],
549 ]);
550
551 let plane_distance = f32::from_le_bytes([
552 mobn_data[offset + 4],
553 mobn_data[offset + 5],
554 mobn_data[offset + 6],
555 mobn_data[offset + 7],
556 ]);
557
558 let child0 = i16::from_le_bytes([mobn_data[offset + 8], mobn_data[offset + 9]]);
559
560 let child1 = i16::from_le_bytes([mobn_data[offset + 10], mobn_data[offset + 11]]);
561
562 let first_face = u16::from_le_bytes([mobn_data[offset + 12], mobn_data[offset + 13]]);
563
564 let num_faces = u16::from_le_bytes([mobn_data[offset + 14], mobn_data[offset + 15]]);
565
566 let plane_flags = (plane_normal_x as u32) & 0x3;
568 let normal = match plane_flags {
569 0 => Vec3 {
570 x: 1.0,
571 y: 0.0,
572 z: 0.0,
573 },
574 1 => Vec3 {
575 x: 0.0,
576 y: 1.0,
577 z: 0.0,
578 },
579 2 => Vec3 {
580 x: 0.0,
581 y: 0.0,
582 z: 1.0,
583 },
584 3 => {
585 let x = ((plane_normal_x as u32) >> 2) as f32 / 32767.0;
587
588 let y = if child0 < 0 { -0.5 } else { 0.5 };
591 let z = if child1 < 0 { -0.5 } else { 0.5 };
592
593 Vec3 { x, y, z }
594 }
595 _ => unreachable!(),
596 };
597
598 nodes.push(WmoBspNode {
599 plane: WmoPlane {
600 normal,
601 distance: plane_distance,
602 },
603 children: [child0, child1],
604 first_face,
605 num_faces,
606 });
607 }
608
609 Ok(Some(nodes))
610 }
611
612 fn parse_liquid<R: Read + Seek>(
614 &self,
615 chunks: &HashMap<ChunkId, Chunk>,
616 reader: &mut R,
617 version: WmoVersion,
618 ) -> Result<Option<WmoLiquid>> {
619 let mliq_chunk = match chunks.get(&chunks::MLIQ) {
620 Some(chunk) => chunk,
621 None => return Ok(None), };
623
624 mliq_chunk.seek_to_data(reader)?;
625
626 let liquid_type = reader.read_u32_le()?;
628 let flags = reader.read_u32_le()?;
629
630 let width = reader.read_u32_le()? + 1; let height = reader.read_u32_le()? + 1;
632
633 reader.seek(SeekFrom::Current(24))?;
635
636 let vertex_count = (width * height) as usize;
639 let mut vertices = Vec::with_capacity(vertex_count);
640
641 if version >= WmoVersion::Wod && version.supports_feature(WmoFeature::LiquidV2) {
642 for y in 0..height {
645 for x in 0..width {
646 let base_x = reader.read_f32_le()?;
647 let base_y = reader.read_f32_le()?;
648 let base_z = reader.read_f32_le()?;
649
650 let height = reader.read_f32_le()?;
651
652 vertices.push(WmoLiquidVertex {
653 position: Vec3 {
654 x: base_x + x as f32,
655 y: base_y + y as f32,
656 z: base_z,
657 },
658 height,
659 });
660 }
661 }
662
663 let has_tile_flags = flags & 2 != 0;
665 let tile_flags = if has_tile_flags {
666 let tile_count = ((width - 1) * (height - 1)) as usize;
667 let mut flags = Vec::with_capacity(tile_count);
668
669 for _ in 0..tile_count {
670 flags.push(reader.read_u8()?);
671 }
672
673 Some(flags)
674 } else {
675 None
676 };
677
678 Ok(Some(WmoLiquid {
679 liquid_type,
680 flags,
681 width,
682 height,
683 vertices,
684 tile_flags,
685 }))
686 } else {
687 for y in 0..height {
690 for x in 0..width {
691 let depth = reader.read_f32_le()?;
692
693 vertices.push(WmoLiquidVertex {
694 position: Vec3 {
695 x: x as f32,
696 y: y as f32,
697 z: 0.0, },
699 height: depth,
700 });
701 }
702 }
703
704 let tile_count = ((width - 1) * (height - 1)) as usize;
706 let mut tile_flags = Vec::with_capacity(tile_count);
707
708 for _ in 0..tile_count {
709 tile_flags.push(reader.read_u8()?);
710 }
711
712 Ok(Some(WmoLiquid {
713 liquid_type,
714 flags,
715 width,
716 height,
717 vertices,
718 tile_flags: Some(tile_flags),
719 }))
720 }
721 }
722
723 fn parse_doodad_refs<R: Read + Seek>(
725 &self,
726 chunks: &HashMap<ChunkId, Chunk>,
727 reader: &mut R,
728 ) -> Result<Vec<u16>> {
729 let modr_chunk = match chunks.get(&chunks::MODR) {
730 Some(chunk) => chunk,
731 None => return Ok(Vec::new()), };
733
734 let modr_data = modr_chunk.read_data(reader)?;
735 let doodad_count = modr_data.len() / 2; let mut doodad_refs = Vec::with_capacity(doodad_count);
737
738 for i in 0..doodad_count {
739 let offset = i * 2;
740
741 let doodad_ref = u16::from_le_bytes([modr_data[offset], modr_data[offset + 1]]);
742
743 doodad_refs.push(doodad_ref);
744 }
745
746 Ok(doodad_refs)
747 }
748}