1use crate::chunk_discovery::ChunkDiscovery;
2use crate::chunk_header::ChunkHeader;
3use crate::chunks::{
4 MliqHeader, MobaEntry, MobnEntry, MobsEntry, MocvEntry, MonrEntry, MopyEntry, MorbEntry,
5 MotaEntry, MotvEntry, MovtEntry, Mpy2Entry,
6};
7use crate::error::{Result, WmoError};
8use crate::wmo_group_types::WmoGroup as LegacyWmoGroup;
9use binrw::{BinRead, BinReaderExt};
10use std::io::{Read, Seek, SeekFrom};
11
12#[derive(Debug, Clone)]
14pub struct WmoGroup {
15 pub version: u32,
17 pub group_index: u32,
19 pub group_name_index: u32,
21 pub descriptive_name_index: u32,
23 pub flags: u32,
25 pub bounding_box: Vec<f32>,
27 pub portal_start: u16,
29 pub portal_count: u16,
30 pub trans_batch_count: u16,
32 pub int_batch_count: u16,
33 pub ext_batch_count: u16,
34 pub batch_type_d: u16,
36 pub fog_ids: Vec<u8>,
38 pub group_liquid: u32,
40 pub area_table_id: u32,
42 pub flags2: u32,
44 pub parent_split_group: i16,
46 pub next_split_child: i16,
48 pub n_triangles: u32,
50 pub n_vertices: u32,
52
53 pub material_info: Vec<MopyEntry>,
56 pub vertex_indices: Vec<u16>,
58 pub vertex_positions: Vec<MovtEntry>,
60 pub vertex_normals: Vec<MonrEntry>,
62 pub texture_coords: Vec<MotvEntry>,
64 pub render_batches: Vec<MobaEntry>,
66 pub vertex_colors: Vec<MocvEntry>,
68 pub light_refs: Vec<u16>,
70 pub doodad_refs: Vec<u16>,
72 pub bsp_nodes: Vec<MobnEntry>,
74 pub bsp_face_indices: Vec<u16>,
76 pub liquid_header: Option<MliqHeader>,
78 pub query_face_start: Option<u32>,
80 pub extended_materials: Vec<Mpy2Entry>,
82 pub extended_vertex_indices: Vec<u32>,
84 pub query_faces: Vec<u32>,
86 pub triangle_strip_indices: Vec<u16>,
88 pub additional_render_batches: Vec<MorbEntry>,
90 pub tangent_arrays: Vec<MotaEntry>,
92 pub shadow_batches: Vec<MobsEntry>,
94}
95
96#[derive(Debug, Clone, BinRead)]
98#[br(little)]
99pub struct MogpHeader {
100 group_name: u32, descriptive_group_name: u32, flags: u32, #[br(count = 6)]
104 bounding_box: Vec<f32>, portal_start: u16, portal_count: u16, trans_batch_count: u16,
108 int_batch_count: u16,
109 ext_batch_count: u16,
110 padding_or_batch_type_d: u16,
111 #[br(count = 4)]
112 fog_ids: Vec<u8>, group_liquid: u32, unique_id: u32, flags2: u32,
116 parent_or_first_child_split_group_index: i16,
117 next_split_child_group_index: i16,
118}
119
120pub fn parse_group_file<R: Read + Seek>(
122 reader: &mut R,
123 discovery: ChunkDiscovery,
124) -> std::result::Result<WmoGroup, Box<dyn std::error::Error>> {
125 let mut group = WmoGroup {
126 version: 0,
127 group_index: 0,
128 group_name_index: 0,
129 descriptive_name_index: 0,
130 flags: 0,
131 bounding_box: Vec::new(),
132 portal_start: 0,
133 portal_count: 0,
134 trans_batch_count: 0,
135 int_batch_count: 0,
136 ext_batch_count: 0,
137 batch_type_d: 0,
138 fog_ids: Vec::new(),
139 group_liquid: 0,
140 area_table_id: 0,
141 flags2: 0,
142 parent_split_group: 0,
143 next_split_child: 0,
144 n_triangles: 0,
145 n_vertices: 0,
146 material_info: Vec::new(),
147 vertex_indices: Vec::new(),
148 vertex_positions: Vec::new(),
149 vertex_normals: Vec::new(),
150 texture_coords: Vec::new(),
151 render_batches: Vec::new(),
152 vertex_colors: Vec::new(),
153 light_refs: Vec::new(),
154 doodad_refs: Vec::new(),
155 bsp_nodes: Vec::new(),
156 bsp_face_indices: Vec::new(),
157 liquid_header: None,
158 query_face_start: None,
159 extended_materials: Vec::new(),
160 extended_vertex_indices: Vec::new(),
161 query_faces: Vec::new(),
162 triangle_strip_indices: Vec::new(),
163 additional_render_batches: Vec::new(),
164 tangent_arrays: Vec::new(),
165 shadow_batches: Vec::new(),
166 };
167
168 for chunk_info in &discovery.chunks {
170 reader.seek(SeekFrom::Start(chunk_info.offset + 8))?;
172
173 match chunk_info.id.as_str() {
174 "MVER" => {
175 group.version = reader.read_le()?;
177 }
178 "MOGP" => {
179 let header = MogpHeader::read(reader)?;
181
182 group.group_index = 0; group.group_name_index = header.group_name;
185 group.descriptive_name_index = header.descriptive_group_name;
186 group.flags = header.flags;
187 group.bounding_box = header.bounding_box;
188 group.portal_start = header.portal_start;
189 group.portal_count = header.portal_count;
190 group.trans_batch_count = header.trans_batch_count;
191 group.int_batch_count = header.int_batch_count;
192 group.ext_batch_count = header.ext_batch_count;
193 group.batch_type_d = header.padding_or_batch_type_d;
194 group.fog_ids = header.fog_ids;
195 group.group_liquid = header.group_liquid;
196 group.area_table_id = header.unique_id;
197 group.flags2 = header.flags2;
198 group.parent_split_group = header.parent_or_first_child_split_group_index;
199 group.next_split_child = header.next_split_child_group_index;
200
201 let data_size = chunk_info.size - 68;
205 let mut data_reader = std::io::Cursor::new(read_chunk_data(reader, data_size)?);
206
207 parse_nested_chunks(&mut data_reader, &mut group)?;
209 }
210 "MOPY" => {
211 let count = chunk_info.size / 2; for _ in 0..count {
214 group.material_info.push(MopyEntry::read(reader)?);
215 }
216 }
217 "MOVI" => {
218 let count = chunk_info.size / 2; for _ in 0..count {
221 group.vertex_indices.push(reader.read_le()?);
222 }
223 }
224 "MOVT" => {
225 let count = chunk_info.size / 12; for _ in 0..count {
228 group.vertex_positions.push(MovtEntry::read(reader)?);
229 }
230 }
231 "MONR" => {
232 let count = chunk_info.size / 12; for _ in 0..count {
235 group.vertex_normals.push(MonrEntry::read(reader)?);
236 }
237 }
238 "MOTV" => {
239 let count = chunk_info.size / 8; for _ in 0..count {
242 group.texture_coords.push(MotvEntry::read(reader)?);
243 }
244 }
245 "MOBA" => {
246 let count = chunk_info.size / 16; for _ in 0..count {
249 group.render_batches.push(MobaEntry::read(reader)?);
250 }
251 }
252 "MOCV" => {
253 let count = chunk_info.size / 4; for _ in 0..count {
256 group.vertex_colors.push(MocvEntry::read(reader)?);
257 }
258 }
259 "MOLR" => {
260 let count = chunk_info.size / 2; for _ in 0..count {
263 group.light_refs.push(reader.read_le()?);
264 }
265 }
266 "MODR" => {
267 let count = chunk_info.size / 2; for _ in 0..count {
270 group.doodad_refs.push(reader.read_le()?);
271 }
272 }
273 "MOBN" => {
274 let count = chunk_info.size / 16; for _ in 0..count {
277 group.bsp_nodes.push(MobnEntry::read(reader)?);
278 }
279 }
280 "MOBR" => {
281 let count = chunk_info.size / 2; for _ in 0..count {
284 group.bsp_face_indices.push(reader.read_le()?);
285 }
286 }
287 "MLIQ" => {
288 if chunk_info.size >= 32 {
290 group.liquid_header = Some(MliqHeader::read(reader)?);
291 }
292 }
293 "MOGX" => {
294 if chunk_info.size >= 4 {
296 group.query_face_start = Some(reader.read_le()?);
297 }
298 }
299 "MPY2" => {
300 let count = chunk_info.size / 4; for _ in 0..count {
303 group.extended_materials.push(Mpy2Entry::read(reader)?);
304 }
305 }
306 "MOVX" => {
307 let count = chunk_info.size / 4; for _ in 0..count {
310 group.extended_vertex_indices.push(reader.read_le()?);
311 }
312 }
313 "MOQG" => {
314 let count = chunk_info.size / 4; for _ in 0..count {
317 group.query_faces.push(reader.read_le()?);
318 }
319 }
320 "MORI" => {
321 let count = chunk_info.size / 2; for _ in 0..count {
324 group.triangle_strip_indices.push(reader.read_le()?);
325 }
326 }
327 "MORB" => {
328 let count = chunk_info.size / 10; for _ in 0..count {
331 group
332 .additional_render_batches
333 .push(MorbEntry::read(reader)?);
334 }
335 }
336 "MOTA" => {
337 let count = chunk_info.size / 8; for _ in 0..count {
340 group.tangent_arrays.push(MotaEntry::read(reader)?);
341 }
342 }
343 "MOBS" => {
344 let count = chunk_info.size / 10; for _ in 0..count {
347 group.shadow_batches.push(MobsEntry::read(reader)?);
348 }
349 }
350 _ => {
351 }
353 }
354 }
355
356 group.n_triangles = (group.vertex_indices.len() / 3) as u32;
358 group.n_vertices = group.vertex_positions.len() as u32;
359
360 Ok(group)
361}
362
363pub struct WmoGroupParser;
365
366impl Default for WmoGroupParser {
367 fn default() -> Self {
368 Self
369 }
370}
371
372impl WmoGroupParser {
373 pub fn new() -> Self {
375 Self
376 }
377
378 pub fn parse_group<R: Read + Seek>(
380 &self,
381 _reader: &mut R,
382 _group_index: u32,
383 ) -> Result<LegacyWmoGroup> {
384 Err(WmoError::InvalidFormat(
386 "Legacy parser not yet migrated".into(),
387 ))
388 }
389}
390
391fn read_chunk_data<R: Read>(
393 reader: &mut R,
394 size: u32,
395) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>> {
396 let mut data = vec![0u8; size as usize];
397 reader.read_exact(&mut data)?;
398 Ok(data)
399}
400
401fn parse_nested_chunks<R: Read + Seek>(
403 reader: &mut R,
404 group: &mut WmoGroup,
405) -> std::result::Result<(), Box<dyn std::error::Error>> {
406 let end_pos = reader.seek(SeekFrom::End(0))?;
407 reader.seek(SeekFrom::Start(0))?;
408
409 while reader.stream_position()? < end_pos {
411 let chunk_start = reader.stream_position()?;
412 let remaining = end_pos - chunk_start;
413
414 if remaining < 8 {
415 break; }
417
418 let header = match ChunkHeader::read(reader) {
419 Ok(h) => h,
420 Err(_) => break, };
422
423 let chunk_id = header.id.as_str();
424 let chunk_size = header.size;
425
426 let remaining_data = end_pos - reader.stream_position()?;
428 if chunk_size as u64 > remaining_data {
429 break;
432 }
433 match chunk_id {
434 "MOPY" => {
435 let count = chunk_size / 2; for _ in 0..count {
438 group.material_info.push(MopyEntry::read(reader)?);
439 }
440 }
441 "MOVI" => {
442 let count = chunk_size / 2; for _ in 0..count {
445 group.vertex_indices.push(reader.read_le()?);
446 }
447 }
448 "MOVT" => {
449 let count = chunk_size / 12; for _ in 0..count {
452 group.vertex_positions.push(MovtEntry::read(reader)?);
453 }
454 }
455 "MONR" => {
456 let count = chunk_size / 12; for _ in 0..count {
459 group.vertex_normals.push(MonrEntry::read(reader)?);
460 }
461 }
462 "MOTV" => {
463 let count = chunk_size / 8; for _ in 0..count {
466 group.texture_coords.push(MotvEntry::read(reader)?);
467 }
468 }
469 "MOBA" => {
470 let count = chunk_size / 24; for _ in 0..count {
473 group.render_batches.push(MobaEntry::read(reader)?);
474 }
475 }
476 "MOCV" => {
477 let count = chunk_size / 4; for _ in 0..count {
480 group.vertex_colors.push(MocvEntry::read(reader)?);
481 }
482 }
483 "MOBN" => {
484 let count = chunk_size / 16; for _ in 0..count {
487 group.bsp_nodes.push(MobnEntry::read(reader)?);
488 }
489 }
490 "MOBR" => {
491 let count = chunk_size / 2; for _ in 0..count {
494 group.bsp_face_indices.push(reader.read_le()?);
495 }
496 }
497 "MLIQ" => {
498 if chunk_size >= 32 {
500 group.liquid_header = Some(MliqHeader::read(reader)?);
501 }
502 }
503 "MOGX" => {
504 if chunk_size >= 4 {
506 group.query_face_start = Some(reader.read_le()?);
507 }
508 }
509 "MPY2" => {
510 let count = chunk_size / 4; for _ in 0..count {
513 group.extended_materials.push(Mpy2Entry::read(reader)?);
514 }
515 }
516 "MOVX" => {
517 let count = chunk_size / 4; for _ in 0..count {
520 group.extended_vertex_indices.push(reader.read_le()?);
521 }
522 }
523 "MOQG" => {
524 let count = chunk_size / 4; for _ in 0..count {
527 group.query_faces.push(reader.read_le()?);
528 }
529 }
530 "MORI" => {
531 let count = chunk_size / 2; for _ in 0..count {
534 group.triangle_strip_indices.push(reader.read_le()?);
535 }
536 }
537 "MORB" => {
538 let count = chunk_size / 10; for _ in 0..count {
541 group
542 .additional_render_batches
543 .push(MorbEntry::read(reader)?);
544 }
545 }
546 "MOTA" => {
547 let count = chunk_size / 8; for _ in 0..count {
550 group.tangent_arrays.push(MotaEntry::read(reader)?);
551 }
552 }
553 "MOBS" => {
554 let count = chunk_size / 10; for _ in 0..count {
557 group.shadow_batches.push(MobsEntry::read(reader)?);
558 }
559 }
560 _ => {
561 reader.seek(SeekFrom::Current(chunk_size as i64))?;
563 }
564 }
565 }
566
567 Ok(())
568}