1use crate::converter::WmoConverter;
7use crate::error::{Result, WmoError};
8use crate::types::{BoundingBox, Vec3};
9use crate::version::WmoVersion;
10use crate::wmo_group_types::{WmoGroup, WmoGroupHeader};
11use crate::wmo_types::{WmoDoodadDef, WmoDoodadSet, WmoGroupInfo, WmoMaterial, WmoRoot};
12use crate::writer::WmoWriter;
13
14use crate::wmo_group_types::WmoGroupFlags;
16
17pub struct WmoEditor {
19 root: WmoRoot,
21
22 groups: Vec<WmoGroup>,
24
25 root_modified: bool,
27
28 group_modified: Vec<bool>,
30
31 original_version: WmoVersion,
33}
34
35impl WmoEditor {
36 pub fn new(root: WmoRoot) -> Self {
38 let original_version = root.version;
39 let group_count = root.groups.len();
40
41 Self {
42 root,
43 groups: Vec::with_capacity(group_count),
44 root_modified: false,
45 group_modified: vec![false; group_count],
46 original_version,
47 }
48 }
49
50 pub fn add_group(&mut self, group: WmoGroup) -> Result<()> {
52 let group_index = group.header.group_index as usize;
54
55 if group_index >= self.root.groups.len() {
56 return Err(WmoError::InvalidReference {
57 field: "group_index".to_string(),
58 value: group_index as u32,
59 max: self.root.groups.len() as u32 - 1,
60 });
61 }
62
63 if self.groups.len() <= group_index {
65 self.groups.resize_with(group_index + 1, || WmoGroup {
66 header: WmoGroupHeader {
67 flags: WmoGroupFlags::empty(),
68 bounding_box: BoundingBox {
69 min: Vec3 {
70 x: 0.0,
71 y: 0.0,
72 z: 0.0,
73 },
74 max: Vec3 {
75 x: 0.0,
76 y: 0.0,
77 z: 0.0,
78 },
79 },
80 name_offset: 0,
81 group_index: 0,
82 },
83 materials: Vec::new(),
84 vertices: Vec::new(),
85 normals: Vec::new(),
86 tex_coords: Vec::new(),
87 batches: Vec::new(),
88 indices: Vec::new(),
89 vertex_colors: None,
90 bsp_nodes: None,
91 liquid: None,
92 doodad_refs: None,
93 });
94 }
95
96 self.groups[group_index] = group;
98 self.group_modified[group_index] = true;
99
100 Ok(())
101 }
102
103 pub fn root(&self) -> &WmoRoot {
105 &self.root
106 }
107
108 pub fn root_mut(&mut self) -> &mut WmoRoot {
110 self.root_modified = true;
111 &mut self.root
112 }
113
114 pub fn group(&self, index: usize) -> Option<&WmoGroup> {
116 self.groups.get(index)
117 }
118
119 pub fn group_mut(&mut self, index: usize) -> Option<&mut WmoGroup> {
121 if index < self.group_modified.len() {
122 self.group_modified[index] = true;
123 }
124 self.groups.get_mut(index)
125 }
126
127 pub fn group_count(&self) -> usize {
129 self.root.groups.len()
130 }
131
132 pub fn is_group_loaded(&self, index: usize) -> bool {
134 index < self.groups.len() && self.group(index).is_some()
135 }
136
137 pub fn is_group_modified(&self, index: usize) -> bool {
139 index < self.group_modified.len() && self.group_modified[index]
140 }
141
142 pub fn is_root_modified(&self) -> bool {
144 self.root_modified
145 }
146
147 pub fn convert_to_version(&mut self, target_version: WmoVersion) -> Result<()> {
149 if self.root.version == target_version {
151 return Ok(());
152 }
153
154 let converter = WmoConverter::new();
156 converter.convert_root(&mut self.root, target_version)?;
157 self.root_modified = true;
158
159 for (i, group) in self.groups.iter_mut().enumerate() {
161 converter.convert_group(group, target_version, self.original_version)?;
162 if i < self.group_modified.len() {
163 self.group_modified[i] = true;
164 }
165 }
166
167 Ok(())
168 }
169
170 pub fn original_version(&self) -> WmoVersion {
172 self.original_version
173 }
174
175 pub fn current_version(&self) -> WmoVersion {
177 self.root.version
178 }
179
180 pub fn save_root<W: std::io::Write + std::io::Seek>(&self, writer: &mut W) -> Result<()> {
182 let writer_obj = WmoWriter::new();
184 writer_obj.write_root(writer, &self.root, self.root.version)?;
185
186 Ok(())
187 }
188
189 pub fn save_group<W: std::io::Write + std::io::Seek>(
191 &self,
192 writer: &mut W,
193 index: usize,
194 ) -> Result<()> {
195 let group = self
197 .group(index)
198 .ok_or_else(|| WmoError::InvalidReference {
199 field: "group_index".to_string(),
200 value: index as u32,
201 max: self.groups.len() as u32 - 1,
202 })?;
203
204 let writer_obj = WmoWriter::new();
206 writer_obj.write_group(writer, group, self.root.version)?;
207
208 Ok(())
209 }
210
211 pub fn add_material(&mut self, material: WmoMaterial) -> usize {
215 self.root_modified = true;
216 self.root.materials.push(material);
217 self.root.header.n_materials += 1;
218 self.root.materials.len() - 1
219 }
220
221 pub fn remove_material(&mut self, index: usize) -> Result<WmoMaterial> {
223 if index >= self.root.materials.len() {
224 return Err(WmoError::InvalidReference {
225 field: "material_index".to_string(),
226 value: index as u32,
227 max: self.root.materials.len() as u32 - 1,
228 });
229 }
230
231 self.root_modified = true;
232 let material = self.root.materials.remove(index);
233 self.root.header.n_materials -= 1;
234
235 for (i, group) in self.groups.iter_mut().enumerate() {
237 let mut modified = false;
238
239 for mat_idx in &mut group.materials {
241 match (*mat_idx as usize).cmp(&index) {
242 std::cmp::Ordering::Equal => {
243 *mat_idx = 0;
245 modified = true;
246 }
247 std::cmp::Ordering::Greater => {
248 *mat_idx -= 1;
250 modified = true;
251 }
252 std::cmp::Ordering::Less => {}
253 }
254 }
255
256 for batch in &mut group.batches {
258 match (batch.material_id as usize).cmp(&index) {
259 std::cmp::Ordering::Equal => {
260 batch.material_id = 0;
262 modified = true;
263 }
264 std::cmp::Ordering::Greater => {
265 batch.material_id -= 1;
267 modified = true;
268 }
269 std::cmp::Ordering::Less => {}
270 }
271 }
272
273 if modified && i < self.group_modified.len() {
274 self.group_modified[i] = true;
275 }
276 }
277
278 Ok(material)
279 }
280
281 pub fn material(&self, index: usize) -> Option<&WmoMaterial> {
283 self.root.materials.get(index)
284 }
285
286 pub fn material_mut(&mut self, index: usize) -> Option<&mut WmoMaterial> {
288 self.root_modified = true;
289 self.root.materials.get_mut(index)
290 }
291
292 pub fn add_texture(&mut self, texture: String) -> usize {
296 self.root_modified = true;
297 self.root.textures.push(texture);
298 self.root.textures.len() - 1
299 }
300
301 pub fn remove_texture(&mut self, index: usize) -> Result<String> {
303 if index >= self.root.textures.len() {
304 return Err(WmoError::InvalidReference {
305 field: "texture_index".to_string(),
306 value: index as u32,
307 max: self.root.textures.len() as u32 - 1,
308 });
309 }
310
311 self.root_modified = true;
312 let texture = self.root.textures.remove(index);
313
314 for material in &mut self.root.materials {
316 match (material.texture1 as usize).cmp(&index) {
317 std::cmp::Ordering::Equal => {
318 material.texture1 = 0;
320 }
321 std::cmp::Ordering::Greater => {
322 material.texture1 -= 1;
324 }
325 std::cmp::Ordering::Less => {}
326 }
327
328 match (material.texture2 as usize).cmp(&index) {
329 std::cmp::Ordering::Equal => {
330 material.texture2 = 0;
332 }
333 std::cmp::Ordering::Greater => {
334 material.texture2 -= 1;
336 }
337 std::cmp::Ordering::Less => {}
338 }
339 }
340
341 Ok(texture)
342 }
343
344 pub fn texture(&self, index: usize) -> Option<&String> {
346 self.root.textures.get(index)
347 }
348
349 pub fn texture_mut(&mut self, index: usize) -> Option<&mut String> {
351 self.root_modified = true;
352 self.root.textures.get_mut(index)
353 }
354
355 pub fn create_group(&mut self, name: String) -> usize {
359 self.root_modified = true;
360
361 let group_info = WmoGroupInfo {
363 flags: WmoGroupFlags::empty(),
364 bounding_box: BoundingBox {
365 min: Vec3 {
366 x: 0.0,
367 y: 0.0,
368 z: 0.0,
369 },
370 max: Vec3 {
371 x: 0.0,
372 y: 0.0,
373 z: 0.0,
374 },
375 },
376 name,
377 };
378
379 self.root.groups.push(group_info);
381 self.root.header.n_groups += 1;
382
383 let group_index = self.root.groups.len() - 1;
385 let header = WmoGroupHeader {
386 flags: WmoGroupFlags::empty(),
387 bounding_box: BoundingBox {
388 min: Vec3 {
389 x: 0.0,
390 y: 0.0,
391 z: 0.0,
392 },
393 max: Vec3 {
394 x: 0.0,
395 y: 0.0,
396 z: 0.0,
397 },
398 },
399 name_offset: 0, group_index: group_index as u32,
401 };
402
403 let group = WmoGroup {
404 header,
405 materials: Vec::new(),
406 vertices: Vec::new(),
407 normals: Vec::new(),
408 tex_coords: Vec::new(),
409 batches: Vec::new(),
410 indices: Vec::new(),
411 vertex_colors: None,
412 bsp_nodes: None,
413 liquid: None,
414 doodad_refs: None,
415 };
416
417 if self.groups.len() <= group_index {
419 self.groups.resize_with(group_index + 1, || WmoGroup {
420 header: WmoGroupHeader {
421 flags: WmoGroupFlags::empty(),
422 bounding_box: BoundingBox {
423 min: Vec3 {
424 x: 0.0,
425 y: 0.0,
426 z: 0.0,
427 },
428 max: Vec3 {
429 x: 0.0,
430 y: 0.0,
431 z: 0.0,
432 },
433 },
434 name_offset: 0,
435 group_index: 0,
436 },
437 materials: Vec::new(),
438 vertices: Vec::new(),
439 normals: Vec::new(),
440 tex_coords: Vec::new(),
441 batches: Vec::new(),
442 indices: Vec::new(),
443 vertex_colors: None,
444 bsp_nodes: None,
445 liquid: None,
446 doodad_refs: None,
447 });
448 }
449
450 self.groups.push(group);
451 self.group_modified.push(true);
452
453 group_index
454 }
455
456 pub fn remove_group(&mut self, index: usize) -> Result<WmoGroupInfo> {
458 if index >= self.root.groups.len() {
459 return Err(WmoError::InvalidReference {
460 field: "group_index".to_string(),
461 value: index as u32,
462 max: self.root.groups.len() as u32 - 1,
463 });
464 }
465
466 self.root_modified = true;
467 let group_info = self.root.groups.remove(index);
468 self.root.header.n_groups -= 1;
469
470 for (i, _group) in self.root.groups.iter_mut().enumerate() {
472 if i >= index {
473 let group_idx = i as u32;
475
476 if let Some(loaded_group) = self.groups.get_mut(i) {
477 loaded_group.header.group_index = group_idx;
478 if i < self.group_modified.len() {
479 self.group_modified[i] = true;
480 }
481 }
482 }
483 }
484
485 if index < self.groups.len() {
487 self.groups.remove(index);
488 }
489
490 if index < self.group_modified.len() {
491 self.group_modified.remove(index);
492 }
493
494 for portal_ref in &mut self.root.portal_references {
496 match (portal_ref.group_index as usize).cmp(&index) {
497 std::cmp::Ordering::Equal => {
498 portal_ref.group_index = 0;
501 }
502 std::cmp::Ordering::Greater => {
503 portal_ref.group_index -= 1;
505 }
506 std::cmp::Ordering::Less => {}
507 }
508 }
509
510 Ok(group_info)
511 }
512
513 pub fn add_vertex(&mut self, group_index: usize, vertex: Vec3) -> Result<usize> {
517 if group_index >= self.groups.len() {
519 return Err(WmoError::InvalidReference {
520 field: "group_index".to_string(),
521 value: group_index as u32,
522 max: self.groups.len() as u32 - 1,
523 });
524 }
525
526 let vertex_index = {
528 let group = &mut self.groups[group_index];
529
530 group.vertices.push(vertex);
532
533 let min = &mut group.header.bounding_box.min;
535 let max = &mut group.header.bounding_box.max;
536
537 min.x = min.x.min(vertex.x);
538 min.y = min.y.min(vertex.y);
539 min.z = min.z.min(vertex.z);
540
541 max.x = max.x.max(vertex.x);
542 max.y = max.y.max(vertex.y);
543 max.z = max.z.max(vertex.z);
544
545 group.vertices.len() - 1
546 };
547
548 if let Some(group_info) = self.root.groups.get_mut(group_index) {
550 let info_min = &mut group_info.bounding_box.min;
551 let info_max = &mut group_info.bounding_box.max;
552
553 info_min.x = info_min.x.min(vertex.x);
554 info_min.y = info_min.y.min(vertex.y);
555 info_min.z = info_min.z.min(vertex.z);
556
557 info_max.x = info_max.x.max(vertex.x);
558 info_max.y = info_max.y.max(vertex.y);
559 info_max.z = info_max.z.max(vertex.z);
560
561 self.root_modified = true;
562 }
563
564 Ok(vertex_index)
565 }
566
567 pub fn remove_vertex(&mut self, group_index: usize, vertex_index: usize) -> Result<Vec3> {
569 if group_index >= self.groups.len() {
571 return Err(WmoError::InvalidReference {
572 field: "group_index".to_string(),
573 value: group_index as u32,
574 max: self.groups.len() as u32 - 1,
575 });
576 }
577
578 let group = &mut self.groups[group_index];
579
580 if vertex_index >= group.vertices.len() {
581 return Err(WmoError::InvalidReference {
582 field: "vertex_index".to_string(),
583 value: vertex_index as u32,
584 max: group.vertices.len() as u32 - 1,
585 });
586 }
587
588 let vertex = group.vertices.remove(vertex_index);
590
591 if vertex_index < group.normals.len() {
593 group.normals.remove(vertex_index);
594 }
595
596 if vertex_index < group.tex_coords.len() {
598 group.tex_coords.remove(vertex_index);
599 }
600
601 if let Some(colors) = &mut group.vertex_colors
603 && vertex_index < colors.len()
604 {
605 colors.remove(vertex_index);
606 }
607
608 for idx in &mut group.indices {
610 match (*idx as usize).cmp(&vertex_index) {
611 std::cmp::Ordering::Equal => {
612 *idx = 0;
615 }
616 std::cmp::Ordering::Greater => {
617 *idx -= 1;
619 }
620 std::cmp::Ordering::Less => {}
621 }
622 }
623
624 self.recalculate_group_bounding_box(group_index)?;
626
627 Ok(vertex)
628 }
629
630 pub fn recalculate_group_bounding_box(&mut self, group_index: usize) -> Result<()> {
632 if group_index >= self.groups.len() {
634 return Err(WmoError::InvalidReference {
635 field: "group_index".to_string(),
636 value: group_index as u32,
637 max: self.groups.len() as u32 - 1,
638 });
639 }
640
641 let new_bounding_box = {
643 let group = &mut self.groups[group_index];
644
645 if group.vertices.is_empty() {
646 BoundingBox {
648 min: Vec3 {
649 x: 0.0,
650 y: 0.0,
651 z: 0.0,
652 },
653 max: Vec3 {
654 x: 0.0,
655 y: 0.0,
656 z: 0.0,
657 },
658 }
659 } else {
660 let mut min_x = f32::MAX;
662 let mut min_y = f32::MAX;
663 let mut min_z = f32::MAX;
664 let mut max_x = f32::MIN;
665 let mut max_y = f32::MIN;
666 let mut max_z = f32::MIN;
667
668 for vertex in &group.vertices {
669 min_x = min_x.min(vertex.x);
670 min_y = min_y.min(vertex.y);
671 min_z = min_z.min(vertex.z);
672
673 max_x = max_x.max(vertex.x);
674 max_y = max_y.max(vertex.y);
675 max_z = max_z.max(vertex.z);
676 }
677
678 BoundingBox {
679 min: Vec3 {
680 x: min_x,
681 y: min_y,
682 z: min_z,
683 },
684 max: Vec3 {
685 x: max_x,
686 y: max_y,
687 z: max_z,
688 },
689 }
690 }
691 };
692
693 self.groups[group_index].header.bounding_box = new_bounding_box;
695
696 if let Some(group_info) = self.root.groups.get_mut(group_index) {
698 group_info.bounding_box = new_bounding_box;
699 self.root_modified = true;
700 }
701
702 Ok(())
703 }
704
705 pub fn recalculate_global_bounding_box(&mut self) -> Result<()> {
707 if self.root.groups.is_empty() {
708 self.root.bounding_box = BoundingBox {
710 min: Vec3 {
711 x: 0.0,
712 y: 0.0,
713 z: 0.0,
714 },
715 max: Vec3 {
716 x: 0.0,
717 y: 0.0,
718 z: 0.0,
719 },
720 };
721 } else {
722 let mut min_x = f32::MAX;
724 let mut min_y = f32::MAX;
725 let mut min_z = f32::MAX;
726 let mut max_x = f32::MIN;
727 let mut max_y = f32::MIN;
728 let mut max_z = f32::MIN;
729
730 for group_info in &self.root.groups {
731 min_x = min_x.min(group_info.bounding_box.min.x);
732 min_y = min_y.min(group_info.bounding_box.min.y);
733 min_z = min_z.min(group_info.bounding_box.min.z);
734
735 max_x = max_x.max(group_info.bounding_box.max.x);
736 max_y = max_y.max(group_info.bounding_box.max.y);
737 max_z = max_z.max(group_info.bounding_box.max.z);
738 }
739
740 self.root.bounding_box = BoundingBox {
741 min: Vec3 {
742 x: min_x,
743 y: min_y,
744 z: min_z,
745 },
746 max: Vec3 {
747 x: max_x,
748 y: max_y,
749 z: max_z,
750 },
751 };
752 }
753
754 self.root_modified = true;
755 Ok(())
756 }
757
758 pub fn add_doodad(&mut self, doodad: WmoDoodadDef) -> usize {
762 self.root_modified = true;
763 self.root.doodad_defs.push(doodad);
764 self.root.header.n_doodad_defs += 1;
765 self.root.header.n_doodad_names += 1; self.root.doodad_defs.len() - 1
768 }
769
770 pub fn remove_doodad(&mut self, index: usize) -> Result<WmoDoodadDef> {
772 if index >= self.root.doodad_defs.len() {
773 return Err(WmoError::InvalidReference {
774 field: "doodad_index".to_string(),
775 value: index as u32,
776 max: self.root.doodad_defs.len() as u32 - 1,
777 });
778 }
779
780 self.root_modified = true;
781 let doodad = self.root.doodad_defs.remove(index);
782 self.root.header.n_doodad_defs -= 1;
783
784 for set in &mut self.root.doodad_sets {
786 if set.start_doodad as usize <= index
787 && index < (set.start_doodad + set.n_doodads) as usize
788 {
789 set.n_doodads -= 1;
791 }
792
793 if set.start_doodad as usize > index {
794 set.start_doodad -= 1;
796 }
797 }
798
799 for (i, group) in self.groups.iter_mut().enumerate() {
801 if let Some(refs) = &mut group.doodad_refs {
802 let mut modified = false;
803
804 for doodad_ref in refs.iter_mut() {
805 match (*doodad_ref as usize).cmp(&index) {
806 std::cmp::Ordering::Equal => {
807 *doodad_ref = 0;
810 modified = true;
811 }
812 std::cmp::Ordering::Greater => {
813 *doodad_ref -= 1;
815 modified = true;
816 }
817 std::cmp::Ordering::Less => {
818 }
820 }
821 }
822
823 if modified && i < self.group_modified.len() {
824 self.group_modified[i] = true;
825 }
826 }
827 }
828
829 Ok(doodad)
830 }
831
832 pub fn add_doodad_set(&mut self, set: WmoDoodadSet) -> usize {
835 self.root_modified = true;
836 self.root.doodad_sets.push(set);
837 self.root.header.n_doodad_sets += 1;
838
839 self.root.doodad_sets.len() - 1
840 }
841
842 pub fn remove_doodad_set(&mut self, index: usize) -> Result<WmoDoodadSet> {
844 if index >= self.root.doodad_sets.len() {
845 return Err(WmoError::InvalidReference {
846 field: "doodad_set_index".to_string(),
847 value: index as u32,
848 max: self.root.doodad_sets.len() as u32 - 1,
849 });
850 }
851
852 self.root_modified = true;
853 let set = self.root.doodad_sets.remove(index);
854 self.root.header.n_doodad_sets -= 1;
855
856 Ok(set)
857 }
858}