1use std::collections::HashMap;
2use std::fs::File;
3use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
4
5use crate::io_ext::ReadExt;
6use std::path::Path;
7
8use crate::chunks::animation::{M2Animation, M2AnimationBlock};
9use crate::chunks::attachment::M2Attachment;
10use crate::chunks::bone::M2Bone;
11use crate::chunks::camera::M2Camera;
12use crate::chunks::color_animation::M2ColorAnimation;
13use crate::chunks::event::M2Event;
14use crate::chunks::infrastructure::{ChunkHeader, ChunkReader};
15use crate::chunks::light::M2Light;
16use crate::chunks::m2_track::{M2Track, M2TrackQuat, M2TrackVec3};
17use crate::chunks::material::M2Material;
18use crate::chunks::particle_emitter::M2ParticleEmitter;
19use crate::chunks::ribbon_emitter::M2RibbonEmitter;
20use crate::chunks::texture_animation::M2TextureAnimation;
21use crate::chunks::transparency_animation::M2TransparencyAnimation;
22use crate::chunks::{
23 AfraChunk, AnimationFileIds, BoneData, BoneFileIds, CollisionMeshData, DbocChunk, DpivChunk,
24 EdgeFadeData, ExtendedParticleData, GeometryParticleIds, LightingDetails, LodData, M2Texture,
25 M2Vertex, ModelAlphaData, ParentAnimationBlacklist, ParentAnimationData, ParentEventData,
26 ParentSequenceBounds, ParticleGeosetData, PhysicsData, PhysicsFileDataChunk, PhysicsFileId,
27 RecursiveParticleIds, SkeletonData, SkeletonFileId, SkinFileIds, TextureAnimationChunk,
28 TextureFileIds, WaterfallEffect,
29};
30use crate::common::{M2Array, M2Parse, read_array, read_raw_bytes};
31use crate::error::{M2Error, Result};
32use crate::file_resolver::FileResolver;
33use crate::header::{M2_MAGIC_CHUNKED, M2_MAGIC_LEGACY, M2Header, M2ModelFlags};
34use crate::version::M2Version;
35
36#[derive(Debug, Clone)]
38pub enum M2Format {
39 Legacy(M2Model),
41 Chunked(M2Model),
43}
44
45#[derive(Debug, Clone)]
47pub struct M2Model {
48 pub header: M2Header,
50 pub name: Option<String>,
52 pub global_sequences: Vec<u32>,
54 pub animations: Vec<M2Animation>,
56 pub animation_lookup: Vec<u16>,
58 pub bones: Vec<M2Bone>,
60 pub key_bone_lookup: Vec<u16>,
62 pub vertices: Vec<M2Vertex>,
64 pub textures: Vec<M2Texture>,
66 pub materials: Vec<M2Material>,
68 pub particle_emitters: Vec<M2ParticleEmitter>,
70 pub ribbon_emitters: Vec<M2RibbonEmitter>,
72 pub texture_animations: Vec<M2TextureAnimation>,
74 pub color_animations: Vec<M2ColorAnimation>,
76 pub transparency_animations: Vec<M2TransparencyAnimation>,
78 pub events: Vec<M2Event>,
80 pub attachments: Vec<M2Attachment>,
82 pub cameras: Vec<M2Camera>,
84 pub lights: Vec<M2Light>,
86 pub raw_data: M2RawData,
89
90 pub skin_file_ids: Option<SkinFileIds>,
93 pub animation_file_ids: Option<AnimationFileIds>,
95 pub texture_file_ids: Option<TextureFileIds>,
97 pub physics_file_id: Option<PhysicsFileId>,
99 pub skeleton_file_id: Option<SkeletonFileId>,
101 pub bone_file_ids: Option<BoneFileIds>,
103 pub lod_data: Option<LodData>,
105
106 pub extended_particle_data: Option<ExtendedParticleData>,
109 pub parent_animation_blacklist: Option<ParentAnimationBlacklist>,
111 pub parent_animation_data: Option<ParentAnimationData>,
113 pub waterfall_effect: Option<WaterfallEffect>,
115 pub edge_fade_data: Option<EdgeFadeData>,
117 pub model_alpha_data: Option<ModelAlphaData>,
119 pub lighting_details: Option<LightingDetails>,
121 pub recursive_particle_ids: Option<RecursiveParticleIds>,
123 pub geometry_particle_ids: Option<GeometryParticleIds>,
125
126 pub texture_animation_chunk: Option<TextureAnimationChunk>,
129 pub particle_geoset_data: Option<ParticleGeosetData>,
131 pub dboc_chunk: Option<DbocChunk>,
133 pub afra_chunk: Option<AfraChunk>,
135 pub dpiv_chunk: Option<DpivChunk>,
137 pub parent_sequence_bounds: Option<ParentSequenceBounds>,
139 pub parent_event_data: Option<ParentEventData>,
141 pub collision_mesh_data: Option<CollisionMeshData>,
143 pub physics_file_data: Option<PhysicsFileDataChunk>,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
149pub enum TrackType {
150 #[default]
152 Translation,
153 Rotation,
155 Scale,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
161pub enum ParticleTrackType {
162 #[default]
164 EmissionSpeed,
165 EmissionRate,
167 EmissionArea,
169 XYScale,
171 ZScale,
173 Color,
175 Transparency,
177 Size,
179 Intensity,
181 ZSource,
183}
184
185impl ParticleTrackType {
186 pub fn value_size(&self) -> usize {
188 match self {
189 ParticleTrackType::XYScale => 8, ParticleTrackType::Color => 12, _ => 4, }
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
198pub enum RibbonTrackType {
199 #[default]
201 Color,
202 Alpha,
204 HeightAbove,
206 HeightBelow,
208}
209
210impl RibbonTrackType {
211 pub fn value_size(&self) -> usize {
213 match self {
214 RibbonTrackType::Color => 12, _ => 4, }
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
222pub enum TextureTrackType {
223 #[default]
225 TranslationU,
226 TranslationV,
228 Rotation,
230 ScaleU,
232 ScaleV,
234}
235
236impl TextureTrackType {
237 pub fn value_size(&self) -> usize {
239 4 }
241}
242
243#[derive(Debug, Clone, Default)]
248pub struct BoneAnimationRaw {
249 pub bone_index: usize,
251 pub track_type: TrackType,
253 pub timestamps: Vec<u8>,
255 pub values: Vec<u8>,
257 pub ranges: Option<Vec<u8>>,
259 pub original_timestamps_offset: u32,
261 pub original_values_offset: u32,
263 pub original_ranges_offset: Option<u32>,
265}
266
267#[derive(Debug, Clone, Default)]
276pub struct EmbeddedSkinRaw {
277 pub model_view: Vec<u8>,
279 pub indices: Vec<u8>,
281 pub triangles: Vec<u8>,
283 pub properties: Vec<u8>,
285 pub submeshes: Vec<u8>,
287 pub batches: Vec<u8>,
289 pub original_model_view_offset: u32,
291 pub original_indices_offset: u32,
293 pub original_triangles_offset: u32,
294 pub original_properties_offset: u32,
295 pub original_submeshes_offset: u32,
296 pub original_batches_offset: u32,
297}
298
299#[derive(Debug, Clone, Default)]
305pub struct ParticleAnimationRaw {
306 pub emitter_index: usize,
308 pub track_type: ParticleTrackType,
310 pub interpolation_ranges: Vec<u8>,
312 pub timestamps: Vec<u8>,
314 pub values: Vec<u8>,
316 pub original_ranges_offset: u32,
318 pub original_timestamps_offset: u32,
320 pub original_values_offset: u32,
322}
323
324#[derive(Debug, Clone, Default)]
330pub struct RibbonAnimationRaw {
331 pub emitter_index: usize,
333 pub track_type: RibbonTrackType,
335 pub interpolation_ranges: Vec<u8>,
337 pub timestamps: Vec<u8>,
339 pub values: Vec<u8>,
341 pub original_ranges_offset: u32,
343 pub original_timestamps_offset: u32,
345 pub original_values_offset: u32,
347}
348
349#[derive(Debug, Clone, Default)]
355pub struct TextureAnimationRaw {
356 pub animation_index: usize,
358 pub track_type: TextureTrackType,
360 pub interpolation_ranges: Vec<u8>,
362 pub timestamps: Vec<u8>,
364 pub values: Vec<u8>,
366 pub original_ranges_offset: u32,
368 pub original_timestamps_offset: u32,
370 pub original_values_offset: u32,
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
376pub enum ColorTrackType {
377 #[default]
379 Color,
380 Alpha,
382}
383
384impl ColorTrackType {
385 pub fn value_size(&self) -> usize {
387 match self {
388 ColorTrackType::Color => 12, ColorTrackType::Alpha => 2, }
391 }
392}
393
394#[derive(Debug, Clone, Default)]
400pub struct ColorAnimationRaw {
401 pub animation_index: usize,
403 pub track_type: ColorTrackType,
405 pub interpolation_ranges: Vec<u8>,
407 pub timestamps: Vec<u8>,
409 pub values: Vec<u8>,
411 pub original_ranges_offset: u32,
413 pub original_timestamps_offset: u32,
415 pub original_values_offset: u32,
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
421pub enum TransparencyTrackType {
422 #[default]
424 Alpha,
425}
426
427impl TransparencyTrackType {
428 pub fn value_size(&self) -> usize {
430 4 }
432}
433
434#[derive(Debug, Clone, Default)]
440pub struct TransparencyAnimationRaw {
441 pub animation_index: usize,
443 pub track_type: TransparencyTrackType,
445 pub interpolation_ranges: Vec<u8>,
447 pub timestamps: Vec<u8>,
449 pub values: Vec<u8>,
451 pub original_ranges_offset: u32,
453 pub original_timestamps_offset: u32,
455 pub original_values_offset: u32,
457}
458
459#[derive(Debug, Clone, Default)]
465pub struct EventRaw {
466 pub event_index: usize,
468 pub ranges: Vec<u8>,
470 pub original_ranges_offset: u32,
472 pub timestamps: Vec<u8>,
474 pub original_timestamps_offset: u32,
476}
477
478#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
480pub enum AttachmentTrackType {
481 #[default]
483 Scale,
484}
485
486impl AttachmentTrackType {
487 pub fn value_size(&self) -> usize {
489 4 }
491}
492
493#[derive(Debug, Clone, Default)]
498pub struct AttachmentAnimationRaw {
499 pub attachment_index: usize,
501 pub track_type: AttachmentTrackType,
503 pub interpolation_ranges: Vec<u8>,
505 pub timestamps: Vec<u8>,
507 pub values: Vec<u8>,
509 pub original_ranges_offset: u32,
511 pub original_timestamps_offset: u32,
513 pub original_values_offset: u32,
515}
516
517#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
519pub enum CameraTrackType {
520 #[default]
522 Position,
523 TargetPosition,
525 Roll,
527}
528
529impl CameraTrackType {
530 pub fn value_size(&self) -> usize {
532 match self {
533 CameraTrackType::Position | CameraTrackType::TargetPosition => 12, CameraTrackType::Roll => 4, }
536 }
537}
538
539#[derive(Debug, Clone, Default)]
545pub struct CameraAnimationRaw {
546 pub camera_index: usize,
548 pub track_type: CameraTrackType,
550 pub interpolation_ranges: Vec<u8>,
552 pub timestamps: Vec<u8>,
554 pub values: Vec<u8>,
556 pub original_ranges_offset: u32,
558 pub original_timestamps_offset: u32,
560 pub original_values_offset: u32,
562}
563
564#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
566pub enum LightTrackType {
567 #[default]
569 AmbientColor,
570 DiffuseColor,
572 AttenuationStart,
574 AttenuationEnd,
576 Visibility,
578}
579
580impl LightTrackType {
581 pub fn value_size(&self) -> usize {
583 match self {
584 LightTrackType::AmbientColor | LightTrackType::DiffuseColor => 12, LightTrackType::AttenuationStart
586 | LightTrackType::AttenuationEnd
587 | LightTrackType::Visibility => 4, }
589 }
590}
591
592#[derive(Debug, Clone, Default)]
598pub struct LightAnimationRaw {
599 pub light_index: usize,
601 pub track_type: LightTrackType,
603 pub interpolation_ranges: Vec<u8>,
605 pub timestamps: Vec<u8>,
607 pub values: Vec<u8>,
609 pub original_ranges_offset: u32,
611 pub original_timestamps_offset: u32,
613 pub original_values_offset: u32,
615}
616
617#[derive(Debug, Clone, Default)]
619pub struct M2RawData {
620 pub bone_animation_data: Vec<BoneAnimationRaw>,
623 pub embedded_skins: Vec<EmbeddedSkinRaw>,
626 pub particle_animation_data: Vec<ParticleAnimationRaw>,
629 pub ribbon_animation_data: Vec<RibbonAnimationRaw>,
632 pub texture_animation_data: Vec<TextureAnimationRaw>,
635 pub color_animation_data: Vec<ColorAnimationRaw>,
638 pub transparency_animation_data: Vec<TransparencyAnimationRaw>,
641 pub event_data: Vec<EventRaw>,
644 pub attachment_animation_data: Vec<AttachmentAnimationRaw>,
647 pub camera_animation_data: Vec<CameraAnimationRaw>,
650 pub light_animation_data: Vec<LightAnimationRaw>,
653 pub transparency: Vec<u8>,
655 pub texture_animations: Vec<u8>,
657 pub color_animations: Vec<u8>,
659 pub color_replacements: Vec<u8>,
661 pub render_flags: Vec<u8>,
663 pub bone_lookup_table: Vec<u16>,
665 pub texture_lookup_table: Vec<u16>,
667 pub texture_units: Vec<u16>,
669 pub transparency_lookup_table: Vec<u16>,
671 pub texture_animation_lookup: Vec<u16>,
673 pub bounding_triangles: Vec<u8>,
675 pub bounding_vertices: Vec<u8>,
677 pub bounding_normals: Vec<u8>,
679 pub attachments: Vec<u8>,
681 pub attachment_lookup_table: Vec<u16>,
683 pub events: Vec<u8>,
685 pub lights: Vec<u8>,
687 pub cameras: Vec<u8>,
689 pub camera_lookup_table: Vec<u16>,
691 pub ribbon_emitters: Vec<u8>,
693 pub particle_emitters: Vec<u8>,
695 pub views_data: Vec<u8>,
697 pub texture_flipbooks: Option<Vec<u8>>,
699 pub blend_map_overrides: Option<Vec<u8>>,
701 pub texture_combiner_combos: Option<Vec<u8>>,
703 pub texture_transforms: Option<Vec<u8>>,
705}
706
707pub fn parse_m2<R: Read + Seek>(reader: &mut R) -> Result<M2Format> {
709 let mut magic = [0u8; 4];
710 reader.read_exact(&mut magic)?;
711 reader.seek(SeekFrom::Start(0))?;
712
713 match &magic {
714 magic if magic == &M2_MAGIC_LEGACY => Ok(M2Format::Legacy(M2Model::parse_legacy(reader)?)),
715 magic if magic == &M2_MAGIC_CHUNKED => {
716 Ok(M2Format::Chunked(M2Model::parse_chunked(reader)?))
717 }
718 _ => Err(M2Error::InvalidMagicBytes(magic)),
719 }
720}
721
722fn collect_track_data<R: Read + Seek, T>(
727 reader: &mut R,
728 track: &M2Track<T>,
729 version: u32,
730 value_element_size: usize,
731 bone_index: usize,
732 track_type: TrackType,
733) -> Result<Option<BoneAnimationRaw>> {
734 if track.timestamps.is_empty() && track.values.is_empty() {
736 return Ok(None);
737 }
738
739 let timestamps = if !track.timestamps.is_empty() {
741 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
742 } else {
743 Vec::new()
744 };
745
746 let values = if !track.values.is_empty() {
748 read_raw_bytes(reader, &track.values.convert(), value_element_size)?
749 } else {
750 Vec::new()
751 };
752
753 let (ranges, original_ranges_offset) = if version < 264 {
755 if let Some(ref ranges_array) = track.ranges {
756 if !ranges_array.is_empty() {
757 (
758 Some(read_raw_bytes(reader, &ranges_array.convert(), 8)?),
759 Some(ranges_array.offset),
760 )
761 } else {
762 (None, None)
763 }
764 } else {
765 (None, None)
766 }
767 } else {
768 (None, None)
769 };
770
771 Ok(Some(BoneAnimationRaw {
772 bone_index,
773 track_type,
774 timestamps,
775 values,
776 ranges,
777 original_timestamps_offset: track.timestamps.offset,
778 original_values_offset: track.values.offset,
779 original_ranges_offset,
780 }))
781}
782
783fn collect_bone_animation_data<R: Read + Seek>(
788 reader: &mut R,
789 bones: &[M2Bone],
790 version: u32,
791) -> Result<Vec<BoneAnimationRaw>> {
792 let mut animation_data = Vec::new();
793
794 for (bone_idx, bone) in bones.iter().enumerate() {
795 if let Some(data) = collect_track_data(
797 reader,
798 &bone.translation,
799 version,
800 12,
801 bone_idx,
802 TrackType::Translation,
803 )? {
804 animation_data.push(data);
805 }
806
807 if let Some(data) = collect_track_data(
809 reader,
810 &bone.rotation,
811 version,
812 8,
813 bone_idx,
814 TrackType::Rotation,
815 )? {
816 animation_data.push(data);
817 }
818
819 if let Some(data) =
821 collect_track_data(reader, &bone.scale, version, 12, bone_idx, TrackType::Scale)?
822 {
823 animation_data.push(data);
824 }
825 }
826
827 Ok(animation_data)
828}
829
830fn relocate_bone_track_offsets(bone: &mut M2Bone, offset_map: &HashMap<u32, u32>) {
835 fn relocate_or_zero_track<T: Default>(track: &mut M2Track<T>, offset_map: &HashMap<u32, u32>) {
837 if !track.timestamps.is_empty() {
839 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
840 track.timestamps.offset = new_offset;
841 } else {
842 *track = M2Track::default();
844 return;
845 }
846 }
847
848 if !track.values.is_empty() {
850 if let Some(&new_offset) = offset_map.get(&track.values.offset) {
851 track.values.offset = new_offset;
852 } else {
853 *track = M2Track::default();
855 return;
856 }
857 }
858
859 if let Some(ref mut ranges) = track.ranges
861 && !ranges.is_empty()
862 {
863 if let Some(&new_offset) = offset_map.get(&ranges.offset) {
864 ranges.offset = new_offset;
865 } else {
866 *ranges = M2Array::default();
868 }
869 }
870 }
871
872 relocate_or_zero_track(&mut bone.translation, offset_map);
873 relocate_or_zero_track(&mut bone.rotation, offset_map);
874 relocate_or_zero_track(&mut bone.scale, offset_map);
875}
876
877fn relocate_particle_animation_offsets(
882 emitter: &mut M2ParticleEmitter,
883 offset_map: &HashMap<u32, u32>,
884) {
885 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
887 block: &mut M2AnimationBlock<T>,
888 offset_map: &HashMap<u32, u32>,
889 ) {
890 let track = &mut block.track;
891
892 if !track.interpolation_ranges.is_empty() {
894 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
895 track.interpolation_ranges.offset = new_offset;
896 } else {
897 *block = M2AnimationBlock::default();
899 return;
900 }
901 }
902
903 if !track.timestamps.is_empty() {
905 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
906 track.timestamps.offset = new_offset;
907 } else {
908 *block = M2AnimationBlock::default();
910 return;
911 }
912 }
913
914 if !track.values.array.is_empty() {
916 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
917 track.values.array.offset = new_offset;
918 } else {
919 *block = M2AnimationBlock::default();
921 }
922 }
923 }
924
925 relocate_or_zero_animation_block(&mut emitter.emission_speed_animation, offset_map);
926 relocate_or_zero_animation_block(&mut emitter.emission_rate_animation, offset_map);
927 relocate_or_zero_animation_block(&mut emitter.emission_area_animation, offset_map);
928 relocate_or_zero_animation_block(&mut emitter.xy_scale_animation, offset_map);
929 relocate_or_zero_animation_block(&mut emitter.z_scale_animation, offset_map);
930 relocate_or_zero_animation_block(&mut emitter.color_animation, offset_map);
931 relocate_or_zero_animation_block(&mut emitter.transparency_animation, offset_map);
932 relocate_or_zero_animation_block(&mut emitter.size_animation, offset_map);
933 relocate_or_zero_animation_block(&mut emitter.intensity_animation, offset_map);
934 relocate_or_zero_animation_block(&mut emitter.z_source_animation, offset_map);
935}
936
937fn relocate_ribbon_animation_offsets(
942 emitter: &mut M2RibbonEmitter,
943 offset_map: &HashMap<u32, u32>,
944) {
945 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
947 block: &mut M2AnimationBlock<T>,
948 offset_map: &HashMap<u32, u32>,
949 ) {
950 let track = &mut block.track;
951
952 if !track.interpolation_ranges.is_empty() {
954 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
955 track.interpolation_ranges.offset = new_offset;
956 } else {
957 *block = M2AnimationBlock::default();
959 return;
960 }
961 }
962
963 if !track.timestamps.is_empty() {
965 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
966 track.timestamps.offset = new_offset;
967 } else {
968 *block = M2AnimationBlock::default();
970 return;
971 }
972 }
973
974 if !track.values.array.is_empty() {
976 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
977 track.values.array.offset = new_offset;
978 } else {
979 *block = M2AnimationBlock::default();
981 }
982 }
983 }
984
985 relocate_or_zero_animation_block(&mut emitter.color_animation, offset_map);
986 relocate_or_zero_animation_block(&mut emitter.alpha_animation, offset_map);
987 relocate_or_zero_animation_block(&mut emitter.height_above_animation, offset_map);
988 relocate_or_zero_animation_block(&mut emitter.height_below_animation, offset_map);
989}
990
991fn relocate_texture_animation_offsets(
993 animation: &mut M2TextureAnimation,
994 offset_map: &HashMap<u32, u32>,
995) {
996 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
998 block: &mut M2AnimationBlock<T>,
999 offset_map: &HashMap<u32, u32>,
1000 ) {
1001 let track = &mut block.track;
1002
1003 if !track.interpolation_ranges.is_empty() {
1005 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
1006 track.interpolation_ranges.offset = new_offset;
1007 } else {
1008 *block = M2AnimationBlock::default();
1010 return;
1011 }
1012 }
1013
1014 if !track.timestamps.is_empty() {
1016 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
1017 track.timestamps.offset = new_offset;
1018 } else {
1019 *block = M2AnimationBlock::default();
1021 return;
1022 }
1023 }
1024
1025 if !track.values.array.is_empty() {
1027 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
1028 track.values.array.offset = new_offset;
1029 } else {
1030 *block = M2AnimationBlock::default();
1032 }
1033 }
1034 }
1035
1036 relocate_or_zero_animation_block(&mut animation.translation_u, offset_map);
1037 relocate_or_zero_animation_block(&mut animation.translation_v, offset_map);
1038 relocate_or_zero_animation_block(&mut animation.rotation, offset_map);
1039 relocate_or_zero_animation_block(&mut animation.scale_u, offset_map);
1040 relocate_or_zero_animation_block(&mut animation.scale_v, offset_map);
1041}
1042
1043fn relocate_color_animation_offsets(
1045 animation: &mut M2ColorAnimation,
1046 offset_map: &HashMap<u32, u32>,
1047) {
1048 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
1050 block: &mut M2AnimationBlock<T>,
1051 offset_map: &HashMap<u32, u32>,
1052 ) {
1053 let track = &mut block.track;
1054
1055 if !track.interpolation_ranges.is_empty() {
1057 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
1058 track.interpolation_ranges.offset = new_offset;
1059 } else {
1060 *block = M2AnimationBlock::default();
1062 return;
1063 }
1064 }
1065
1066 if !track.timestamps.is_empty() {
1068 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
1069 track.timestamps.offset = new_offset;
1070 } else {
1071 *block = M2AnimationBlock::default();
1073 return;
1074 }
1075 }
1076
1077 if !track.values.array.is_empty() {
1079 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
1080 track.values.array.offset = new_offset;
1081 } else {
1082 *block = M2AnimationBlock::default();
1084 }
1085 }
1086 }
1087
1088 relocate_or_zero_animation_block(&mut animation.color, offset_map);
1089 relocate_or_zero_animation_block(&mut animation.alpha, offset_map);
1090}
1091
1092fn relocate_transparency_animation_offsets(
1094 animation: &mut M2TransparencyAnimation,
1095 offset_map: &HashMap<u32, u32>,
1096) {
1097 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
1099 block: &mut M2AnimationBlock<T>,
1100 offset_map: &HashMap<u32, u32>,
1101 ) {
1102 let track = &mut block.track;
1103
1104 if !track.interpolation_ranges.is_empty() {
1106 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
1107 track.interpolation_ranges.offset = new_offset;
1108 } else {
1109 *block = M2AnimationBlock::default();
1111 return;
1112 }
1113 }
1114
1115 if !track.timestamps.is_empty() {
1117 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
1118 track.timestamps.offset = new_offset;
1119 } else {
1120 *block = M2AnimationBlock::default();
1122 return;
1123 }
1124 }
1125
1126 if !track.values.array.is_empty() {
1128 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
1129 track.values.array.offset = new_offset;
1130 } else {
1131 *block = M2AnimationBlock::default();
1133 }
1134 }
1135 }
1136
1137 relocate_or_zero_animation_block(&mut animation.alpha, offset_map);
1138}
1139
1140fn relocate_event_offset(event: &mut M2Event, offset_map: &HashMap<u32, u32>) {
1144 if event.ranges.count > 0 {
1146 if let Some(&new_offset) = offset_map.get(&event.ranges.offset) {
1147 event.ranges.offset = new_offset;
1148 } else {
1149 event.ranges = M2Array::default();
1150 }
1151 }
1152
1153 if event.times.count > 0 {
1155 if let Some(&new_offset) = offset_map.get(&event.times.offset) {
1156 event.times.offset = new_offset;
1157 } else {
1158 event.times = M2Array::default();
1159 }
1160 }
1161}
1162
1163fn relocate_attachment_animation_offsets(
1167 attachment: &mut M2Attachment,
1168 offset_map: &HashMap<u32, u32>,
1169) {
1170 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
1172 block: &mut M2AnimationBlock<T>,
1173 offset_map: &HashMap<u32, u32>,
1174 ) {
1175 let track = &mut block.track;
1176
1177 if !track.interpolation_ranges.is_empty() {
1179 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
1180 track.interpolation_ranges.offset = new_offset;
1181 } else {
1182 *block = M2AnimationBlock::default();
1184 return;
1185 }
1186 }
1187
1188 if !track.timestamps.is_empty() {
1190 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
1191 track.timestamps.offset = new_offset;
1192 } else {
1193 *block = M2AnimationBlock::default();
1195 return;
1196 }
1197 }
1198
1199 if !track.values.array.is_empty() {
1201 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
1202 track.values.array.offset = new_offset;
1203 } else {
1204 *block = M2AnimationBlock::default();
1206 }
1207 }
1208 }
1209
1210 relocate_or_zero_animation_block(&mut attachment.scale_animation, offset_map);
1211}
1212
1213fn relocate_camera_animation_offsets(camera: &mut M2Camera, offset_map: &HashMap<u32, u32>) {
1217 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
1219 block: &mut M2AnimationBlock<T>,
1220 offset_map: &HashMap<u32, u32>,
1221 ) {
1222 let track = &mut block.track;
1223
1224 if !track.interpolation_ranges.is_empty() {
1226 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
1227 track.interpolation_ranges.offset = new_offset;
1228 } else {
1229 *block = M2AnimationBlock::default();
1231 return;
1232 }
1233 }
1234
1235 if !track.timestamps.is_empty() {
1237 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
1238 track.timestamps.offset = new_offset;
1239 } else {
1240 *block = M2AnimationBlock::default();
1242 return;
1243 }
1244 }
1245
1246 if !track.values.array.is_empty() {
1248 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
1249 track.values.array.offset = new_offset;
1250 } else {
1251 *block = M2AnimationBlock::default();
1253 }
1254 }
1255 }
1256
1257 relocate_or_zero_animation_block(&mut camera.position_animation, offset_map);
1258 relocate_or_zero_animation_block(&mut camera.target_position_animation, offset_map);
1259 relocate_or_zero_animation_block(&mut camera.roll_animation, offset_map);
1260}
1261
1262fn relocate_light_animation_offsets(light: &mut M2Light, offset_map: &HashMap<u32, u32>) {
1267 fn relocate_or_zero_animation_block<T: M2Parse + Default + Clone>(
1269 block: &mut M2AnimationBlock<T>,
1270 offset_map: &HashMap<u32, u32>,
1271 ) {
1272 let track = &mut block.track;
1273
1274 if !track.interpolation_ranges.is_empty() {
1276 if let Some(&new_offset) = offset_map.get(&track.interpolation_ranges.offset) {
1277 track.interpolation_ranges.offset = new_offset;
1278 } else {
1279 *block = M2AnimationBlock::default();
1281 return;
1282 }
1283 }
1284
1285 if !track.timestamps.is_empty() {
1287 if let Some(&new_offset) = offset_map.get(&track.timestamps.offset) {
1288 track.timestamps.offset = new_offset;
1289 } else {
1290 *block = M2AnimationBlock::default();
1292 return;
1293 }
1294 }
1295
1296 if !track.values.array.is_empty() {
1298 if let Some(&new_offset) = offset_map.get(&track.values.array.offset) {
1299 track.values.array.offset = new_offset;
1300 } else {
1301 *block = M2AnimationBlock::default();
1303 }
1304 }
1305 }
1306
1307 relocate_or_zero_animation_block(&mut light.ambient_color_animation, offset_map);
1308 relocate_or_zero_animation_block(&mut light.diffuse_color_animation, offset_map);
1309 relocate_or_zero_animation_block(&mut light.attenuation_start_animation, offset_map);
1310 relocate_or_zero_animation_block(&mut light.attenuation_end_animation, offset_map);
1311 relocate_or_zero_animation_block(&mut light.visibility_animation, offset_map);
1312}
1313
1314fn collect_particle_track_data<R: Read + Seek, T: M2Parse>(
1319 reader: &mut R,
1320 block: &M2AnimationBlock<T>,
1321 emitter_index: usize,
1322 track_type: ParticleTrackType,
1323) -> Result<Option<ParticleAnimationRaw>> {
1324 let track = &block.track;
1325
1326 if track.timestamps.is_empty() && track.values.array.is_empty() {
1328 return Ok(None);
1329 }
1330
1331 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
1333 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
1334 } else {
1335 Vec::new()
1336 };
1337
1338 let timestamps = if !track.timestamps.is_empty() {
1340 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
1341 } else {
1342 Vec::new()
1343 };
1344
1345 let values = if !track.values.array.is_empty() {
1347 read_raw_bytes(
1348 reader,
1349 &track.values.array.convert(),
1350 track_type.value_size(),
1351 )?
1352 } else {
1353 Vec::new()
1354 };
1355
1356 Ok(Some(ParticleAnimationRaw {
1357 emitter_index,
1358 track_type,
1359 interpolation_ranges,
1360 timestamps,
1361 values,
1362 original_ranges_offset: track.interpolation_ranges.offset,
1363 original_timestamps_offset: track.timestamps.offset,
1364 original_values_offset: track.values.array.offset,
1365 }))
1366}
1367
1368fn collect_particle_animation_data<R: Read + Seek>(
1373 reader: &mut R,
1374 emitters: &[M2ParticleEmitter],
1375) -> Result<Vec<ParticleAnimationRaw>> {
1376 let mut animation_data = Vec::new();
1377
1378 for (emitter_idx, emitter) in emitters.iter().enumerate() {
1379 if let Some(data) = collect_particle_track_data(
1381 reader,
1382 &emitter.emission_speed_animation,
1383 emitter_idx,
1384 ParticleTrackType::EmissionSpeed,
1385 )? {
1386 animation_data.push(data);
1387 }
1388
1389 if let Some(data) = collect_particle_track_data(
1391 reader,
1392 &emitter.emission_rate_animation,
1393 emitter_idx,
1394 ParticleTrackType::EmissionRate,
1395 )? {
1396 animation_data.push(data);
1397 }
1398
1399 if let Some(data) = collect_particle_track_data(
1401 reader,
1402 &emitter.emission_area_animation,
1403 emitter_idx,
1404 ParticleTrackType::EmissionArea,
1405 )? {
1406 animation_data.push(data);
1407 }
1408
1409 if let Some(data) = collect_particle_track_data(
1411 reader,
1412 &emitter.xy_scale_animation,
1413 emitter_idx,
1414 ParticleTrackType::XYScale,
1415 )? {
1416 animation_data.push(data);
1417 }
1418
1419 if let Some(data) = collect_particle_track_data(
1421 reader,
1422 &emitter.z_scale_animation,
1423 emitter_idx,
1424 ParticleTrackType::ZScale,
1425 )? {
1426 animation_data.push(data);
1427 }
1428
1429 if let Some(data) = collect_particle_track_data(
1431 reader,
1432 &emitter.color_animation,
1433 emitter_idx,
1434 ParticleTrackType::Color,
1435 )? {
1436 animation_data.push(data);
1437 }
1438
1439 if let Some(data) = collect_particle_track_data(
1441 reader,
1442 &emitter.transparency_animation,
1443 emitter_idx,
1444 ParticleTrackType::Transparency,
1445 )? {
1446 animation_data.push(data);
1447 }
1448
1449 if let Some(data) = collect_particle_track_data(
1451 reader,
1452 &emitter.size_animation,
1453 emitter_idx,
1454 ParticleTrackType::Size,
1455 )? {
1456 animation_data.push(data);
1457 }
1458
1459 if let Some(data) = collect_particle_track_data(
1461 reader,
1462 &emitter.intensity_animation,
1463 emitter_idx,
1464 ParticleTrackType::Intensity,
1465 )? {
1466 animation_data.push(data);
1467 }
1468
1469 if let Some(data) = collect_particle_track_data(
1471 reader,
1472 &emitter.z_source_animation,
1473 emitter_idx,
1474 ParticleTrackType::ZSource,
1475 )? {
1476 animation_data.push(data);
1477 }
1478 }
1479
1480 Ok(animation_data)
1481}
1482
1483fn collect_ribbon_track_data<R: Read + Seek, T: M2Parse>(
1488 reader: &mut R,
1489 block: &M2AnimationBlock<T>,
1490 emitter_index: usize,
1491 track_type: RibbonTrackType,
1492) -> Result<Option<RibbonAnimationRaw>> {
1493 let track = &block.track;
1494
1495 if track.timestamps.is_empty() && track.values.array.is_empty() {
1497 return Ok(None);
1498 }
1499
1500 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
1502 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
1503 } else {
1504 Vec::new()
1505 };
1506
1507 let timestamps = if !track.timestamps.is_empty() {
1509 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
1510 } else {
1511 Vec::new()
1512 };
1513
1514 let values = if !track.values.array.is_empty() {
1516 read_raw_bytes(
1517 reader,
1518 &track.values.array.convert(),
1519 track_type.value_size(),
1520 )?
1521 } else {
1522 Vec::new()
1523 };
1524
1525 Ok(Some(RibbonAnimationRaw {
1526 emitter_index,
1527 track_type,
1528 interpolation_ranges,
1529 timestamps,
1530 values,
1531 original_ranges_offset: track.interpolation_ranges.offset,
1532 original_timestamps_offset: track.timestamps.offset,
1533 original_values_offset: track.values.array.offset,
1534 }))
1535}
1536
1537fn collect_ribbon_animation_data<R: Read + Seek>(
1542 reader: &mut R,
1543 emitters: &[M2RibbonEmitter],
1544) -> Result<Vec<RibbonAnimationRaw>> {
1545 let mut animation_data = Vec::new();
1546
1547 for (emitter_idx, emitter) in emitters.iter().enumerate() {
1548 if let Some(data) = collect_ribbon_track_data(
1550 reader,
1551 &emitter.color_animation,
1552 emitter_idx,
1553 RibbonTrackType::Color,
1554 )? {
1555 animation_data.push(data);
1556 }
1557
1558 if let Some(data) = collect_ribbon_track_data(
1560 reader,
1561 &emitter.alpha_animation,
1562 emitter_idx,
1563 RibbonTrackType::Alpha,
1564 )? {
1565 animation_data.push(data);
1566 }
1567
1568 if let Some(data) = collect_ribbon_track_data(
1570 reader,
1571 &emitter.height_above_animation,
1572 emitter_idx,
1573 RibbonTrackType::HeightAbove,
1574 )? {
1575 animation_data.push(data);
1576 }
1577
1578 if let Some(data) = collect_ribbon_track_data(
1580 reader,
1581 &emitter.height_below_animation,
1582 emitter_idx,
1583 RibbonTrackType::HeightBelow,
1584 )? {
1585 animation_data.push(data);
1586 }
1587 }
1588
1589 Ok(animation_data)
1590}
1591
1592fn collect_texture_track_data<R: Read + Seek, T: M2Parse>(
1594 reader: &mut R,
1595 block: &M2AnimationBlock<T>,
1596 animation_index: usize,
1597 track_type: TextureTrackType,
1598) -> Result<Option<TextureAnimationRaw>> {
1599 let track = &block.track;
1600
1601 if track.timestamps.is_empty() && track.values.array.is_empty() {
1603 return Ok(None);
1604 }
1605
1606 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
1608 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
1609 } else {
1610 Vec::new()
1611 };
1612
1613 let timestamps = if !track.timestamps.is_empty() {
1615 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
1616 } else {
1617 Vec::new()
1618 };
1619
1620 let values = if !track.values.array.is_empty() {
1622 read_raw_bytes(
1623 reader,
1624 &track.values.array.convert(),
1625 track_type.value_size(),
1626 )?
1627 } else {
1628 Vec::new()
1629 };
1630
1631 Ok(Some(TextureAnimationRaw {
1632 animation_index,
1633 track_type,
1634 interpolation_ranges,
1635 timestamps,
1636 values,
1637 original_ranges_offset: track.interpolation_ranges.offset,
1638 original_timestamps_offset: track.timestamps.offset,
1639 original_values_offset: track.values.array.offset,
1640 }))
1641}
1642
1643fn collect_texture_animation_data<R: Read + Seek>(
1648 reader: &mut R,
1649 animations: &[M2TextureAnimation],
1650) -> Result<Vec<TextureAnimationRaw>> {
1651 let mut animation_data = Vec::new();
1652
1653 for (anim_idx, anim) in animations.iter().enumerate() {
1654 if let Some(data) = collect_texture_track_data(
1656 reader,
1657 &anim.translation_u,
1658 anim_idx,
1659 TextureTrackType::TranslationU,
1660 )? {
1661 animation_data.push(data);
1662 }
1663
1664 if let Some(data) = collect_texture_track_data(
1666 reader,
1667 &anim.translation_v,
1668 anim_idx,
1669 TextureTrackType::TranslationV,
1670 )? {
1671 animation_data.push(data);
1672 }
1673
1674 if let Some(data) = collect_texture_track_data(
1676 reader,
1677 &anim.rotation,
1678 anim_idx,
1679 TextureTrackType::Rotation,
1680 )? {
1681 animation_data.push(data);
1682 }
1683
1684 if let Some(data) =
1686 collect_texture_track_data(reader, &anim.scale_u, anim_idx, TextureTrackType::ScaleU)?
1687 {
1688 animation_data.push(data);
1689 }
1690
1691 if let Some(data) =
1693 collect_texture_track_data(reader, &anim.scale_v, anim_idx, TextureTrackType::ScaleV)?
1694 {
1695 animation_data.push(data);
1696 }
1697 }
1698
1699 Ok(animation_data)
1700}
1701
1702fn collect_color_track_data<R: Read + Seek, T: M2Parse>(
1704 reader: &mut R,
1705 block: &M2AnimationBlock<T>,
1706 animation_index: usize,
1707 track_type: ColorTrackType,
1708) -> Result<Option<ColorAnimationRaw>> {
1709 let track = &block.track;
1710
1711 if track.timestamps.is_empty() && track.values.array.is_empty() {
1713 return Ok(None);
1714 }
1715
1716 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
1718 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
1719 } else {
1720 Vec::new()
1721 };
1722
1723 let timestamps = if !track.timestamps.is_empty() {
1725 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
1726 } else {
1727 Vec::new()
1728 };
1729
1730 let values = if !track.values.array.is_empty() {
1732 read_raw_bytes(
1733 reader,
1734 &track.values.array.convert(),
1735 track_type.value_size(),
1736 )?
1737 } else {
1738 Vec::new()
1739 };
1740
1741 Ok(Some(ColorAnimationRaw {
1742 animation_index,
1743 track_type,
1744 interpolation_ranges,
1745 timestamps,
1746 values,
1747 original_ranges_offset: track.interpolation_ranges.offset,
1748 original_timestamps_offset: track.timestamps.offset,
1749 original_values_offset: track.values.array.offset,
1750 }))
1751}
1752
1753fn collect_color_animation_data<R: Read + Seek>(
1758 reader: &mut R,
1759 animations: &[M2ColorAnimation],
1760) -> Result<Vec<ColorAnimationRaw>> {
1761 let mut animation_data = Vec::new();
1762
1763 for (anim_idx, anim) in animations.iter().enumerate() {
1764 if let Some(data) =
1766 collect_color_track_data(reader, &anim.color, anim_idx, ColorTrackType::Color)?
1767 {
1768 animation_data.push(data);
1769 }
1770
1771 if let Some(data) =
1773 collect_color_track_data(reader, &anim.alpha, anim_idx, ColorTrackType::Alpha)?
1774 {
1775 animation_data.push(data);
1776 }
1777 }
1778
1779 Ok(animation_data)
1780}
1781
1782fn collect_transparency_track_data<R: Read + Seek, T: M2Parse>(
1784 reader: &mut R,
1785 block: &M2AnimationBlock<T>,
1786 animation_index: usize,
1787 track_type: TransparencyTrackType,
1788) -> Result<Option<TransparencyAnimationRaw>> {
1789 let track = &block.track;
1790
1791 if track.timestamps.is_empty() && track.values.array.is_empty() {
1793 return Ok(None);
1794 }
1795
1796 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
1798 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
1799 } else {
1800 Vec::new()
1801 };
1802
1803 let timestamps = if !track.timestamps.is_empty() {
1805 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
1806 } else {
1807 Vec::new()
1808 };
1809
1810 let values = if !track.values.array.is_empty() {
1812 read_raw_bytes(
1813 reader,
1814 &track.values.array.convert(),
1815 track_type.value_size(),
1816 )?
1817 } else {
1818 Vec::new()
1819 };
1820
1821 Ok(Some(TransparencyAnimationRaw {
1822 animation_index,
1823 track_type,
1824 interpolation_ranges,
1825 timestamps,
1826 values,
1827 original_ranges_offset: track.interpolation_ranges.offset,
1828 original_timestamps_offset: track.timestamps.offset,
1829 original_values_offset: track.values.array.offset,
1830 }))
1831}
1832
1833fn collect_transparency_animation_data<R: Read + Seek>(
1838 reader: &mut R,
1839 animations: &[M2TransparencyAnimation],
1840) -> Result<Vec<TransparencyAnimationRaw>> {
1841 let mut animation_data = Vec::new();
1842
1843 for (anim_idx, anim) in animations.iter().enumerate() {
1844 if let Some(data) = collect_transparency_track_data(
1846 reader,
1847 &anim.alpha,
1848 anim_idx,
1849 TransparencyTrackType::Alpha,
1850 )? {
1851 animation_data.push(data);
1852 }
1853 }
1854
1855 Ok(animation_data)
1856}
1857
1858fn collect_event_data<R: Read + Seek>(reader: &mut R, events: &[M2Event]) -> Result<Vec<EventRaw>> {
1863 let mut event_data = Vec::new();
1864
1865 for (event_idx, event) in events.iter().enumerate() {
1866 if event.ranges.count == 0 && event.times.count == 0 {
1868 continue;
1869 }
1870
1871 let ranges = if event.ranges.count > 0 {
1873 read_raw_bytes(reader, &event.ranges.convert(), 8)?
1874 } else {
1875 Vec::new()
1876 };
1877
1878 let timestamps = if event.times.count > 0 {
1880 read_raw_bytes(reader, &event.times.convert(), 4)?
1881 } else {
1882 Vec::new()
1883 };
1884
1885 event_data.push(EventRaw {
1886 event_index: event_idx,
1887 ranges,
1888 original_ranges_offset: event.ranges.offset,
1889 timestamps,
1890 original_timestamps_offset: event.times.offset,
1891 });
1892 }
1893
1894 Ok(event_data)
1895}
1896
1897fn collect_attachment_track_data<R: Read + Seek, T: M2Parse>(
1899 reader: &mut R,
1900 block: &M2AnimationBlock<T>,
1901 attachment_index: usize,
1902 track_type: AttachmentTrackType,
1903) -> Result<Option<AttachmentAnimationRaw>> {
1904 let track = &block.track;
1905
1906 if track.timestamps.is_empty() && track.values.array.is_empty() {
1908 return Ok(None);
1909 }
1910
1911 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
1913 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
1914 } else {
1915 Vec::new()
1916 };
1917
1918 let timestamps = if !track.timestamps.is_empty() {
1920 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
1921 } else {
1922 Vec::new()
1923 };
1924
1925 let values = if !track.values.array.is_empty() {
1927 read_raw_bytes(
1928 reader,
1929 &track.values.array.convert(),
1930 track_type.value_size(),
1931 )?
1932 } else {
1933 Vec::new()
1934 };
1935
1936 Ok(Some(AttachmentAnimationRaw {
1937 attachment_index,
1938 track_type,
1939 interpolation_ranges,
1940 timestamps,
1941 values,
1942 original_ranges_offset: track.interpolation_ranges.offset,
1943 original_timestamps_offset: track.timestamps.offset,
1944 original_values_offset: track.values.array.offset,
1945 }))
1946}
1947
1948fn collect_attachment_animation_data<R: Read + Seek>(
1953 reader: &mut R,
1954 attachments: &[M2Attachment],
1955) -> Result<Vec<AttachmentAnimationRaw>> {
1956 let mut animation_data = Vec::new();
1957
1958 for (attach_idx, attachment) in attachments.iter().enumerate() {
1959 if let Some(data) = collect_attachment_track_data(
1961 reader,
1962 &attachment.scale_animation,
1963 attach_idx,
1964 AttachmentTrackType::Scale,
1965 )? {
1966 animation_data.push(data);
1967 }
1968 }
1969
1970 Ok(animation_data)
1971}
1972
1973fn collect_camera_track_data<R: Read + Seek, T: M2Parse>(
1975 reader: &mut R,
1976 block: &M2AnimationBlock<T>,
1977 camera_index: usize,
1978 track_type: CameraTrackType,
1979) -> Result<Option<CameraAnimationRaw>> {
1980 let track = &block.track;
1981
1982 if track.timestamps.is_empty() && track.values.array.is_empty() {
1984 return Ok(None);
1985 }
1986
1987 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
1989 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
1990 } else {
1991 Vec::new()
1992 };
1993
1994 let timestamps = if !track.timestamps.is_empty() {
1996 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
1997 } else {
1998 Vec::new()
1999 };
2000
2001 let values = if !track.values.array.is_empty() {
2003 read_raw_bytes(
2004 reader,
2005 &track.values.array.convert(),
2006 track_type.value_size(),
2007 )?
2008 } else {
2009 Vec::new()
2010 };
2011
2012 Ok(Some(CameraAnimationRaw {
2013 camera_index,
2014 track_type,
2015 interpolation_ranges,
2016 timestamps,
2017 values,
2018 original_ranges_offset: track.interpolation_ranges.offset,
2019 original_timestamps_offset: track.timestamps.offset,
2020 original_values_offset: track.values.array.offset,
2021 }))
2022}
2023
2024fn collect_camera_animation_data<R: Read + Seek>(
2029 reader: &mut R,
2030 cameras: &[M2Camera],
2031) -> Result<Vec<CameraAnimationRaw>> {
2032 let mut animation_data = Vec::new();
2033
2034 for (camera_idx, camera) in cameras.iter().enumerate() {
2035 if let Some(data) = collect_camera_track_data(
2037 reader,
2038 &camera.position_animation,
2039 camera_idx,
2040 CameraTrackType::Position,
2041 )? {
2042 animation_data.push(data);
2043 }
2044
2045 if let Some(data) = collect_camera_track_data(
2047 reader,
2048 &camera.target_position_animation,
2049 camera_idx,
2050 CameraTrackType::TargetPosition,
2051 )? {
2052 animation_data.push(data);
2053 }
2054
2055 if let Some(data) = collect_camera_track_data(
2057 reader,
2058 &camera.roll_animation,
2059 camera_idx,
2060 CameraTrackType::Roll,
2061 )? {
2062 animation_data.push(data);
2063 }
2064 }
2065
2066 Ok(animation_data)
2067}
2068
2069fn collect_light_track_data<R: Read + Seek, T: M2Parse>(
2071 reader: &mut R,
2072 block: &M2AnimationBlock<T>,
2073 light_index: usize,
2074 track_type: LightTrackType,
2075) -> Result<Option<LightAnimationRaw>> {
2076 let track = &block.track;
2077
2078 if track.timestamps.is_empty() && track.values.array.is_empty() {
2080 return Ok(None);
2081 }
2082
2083 let interpolation_ranges = if !track.interpolation_ranges.is_empty() {
2085 read_raw_bytes(reader, &track.interpolation_ranges.convert(), 8)?
2086 } else {
2087 Vec::new()
2088 };
2089
2090 let timestamps = if !track.timestamps.is_empty() {
2092 read_raw_bytes(reader, &track.timestamps.convert(), 4)?
2093 } else {
2094 Vec::new()
2095 };
2096
2097 let values = if !track.values.array.is_empty() {
2099 read_raw_bytes(
2100 reader,
2101 &track.values.array.convert(),
2102 track_type.value_size(),
2103 )?
2104 } else {
2105 Vec::new()
2106 };
2107
2108 Ok(Some(LightAnimationRaw {
2109 light_index,
2110 track_type,
2111 interpolation_ranges,
2112 timestamps,
2113 values,
2114 original_ranges_offset: track.interpolation_ranges.offset,
2115 original_timestamps_offset: track.timestamps.offset,
2116 original_values_offset: track.values.array.offset,
2117 }))
2118}
2119
2120fn collect_light_animation_data<R: Read + Seek>(
2125 reader: &mut R,
2126 lights: &[M2Light],
2127) -> Result<Vec<LightAnimationRaw>> {
2128 let mut animation_data = Vec::new();
2129
2130 for (light_idx, light) in lights.iter().enumerate() {
2131 if let Some(data) = collect_light_track_data(
2133 reader,
2134 &light.ambient_color_animation,
2135 light_idx,
2136 LightTrackType::AmbientColor,
2137 )? {
2138 animation_data.push(data);
2139 }
2140
2141 if let Some(data) = collect_light_track_data(
2143 reader,
2144 &light.diffuse_color_animation,
2145 light_idx,
2146 LightTrackType::DiffuseColor,
2147 )? {
2148 animation_data.push(data);
2149 }
2150
2151 if let Some(data) = collect_light_track_data(
2153 reader,
2154 &light.attenuation_start_animation,
2155 light_idx,
2156 LightTrackType::AttenuationStart,
2157 )? {
2158 animation_data.push(data);
2159 }
2160
2161 if let Some(data) = collect_light_track_data(
2163 reader,
2164 &light.attenuation_end_animation,
2165 light_idx,
2166 LightTrackType::AttenuationEnd,
2167 )? {
2168 animation_data.push(data);
2169 }
2170
2171 if let Some(data) = collect_light_track_data(
2173 reader,
2174 &light.visibility_animation,
2175 light_idx,
2176 LightTrackType::Visibility,
2177 )? {
2178 animation_data.push(data);
2179 }
2180 }
2181
2182 Ok(animation_data)
2183}
2184
2185fn collect_embedded_skin_data<R: Read + Seek>(
2190 reader: &mut R,
2191 header: &M2Header,
2192) -> Result<Vec<EmbeddedSkinRaw>> {
2193 if header.version > 263 {
2196 return Ok(Vec::new());
2197 }
2198
2199 if header.views.count == 0 || header.views.offset == 0 {
2201 return Ok(Vec::new());
2202 }
2203
2204 let mut embedded_skins = Vec::new();
2205 const MODEL_VIEW_SIZE: usize = 44; let submesh_size = if header.version < 260 { 32 } else { 48 };
2209
2210 for view_idx in 0..header.views.count {
2212 let model_view_offset = header.views.offset + (view_idx * MODEL_VIEW_SIZE as u32);
2213
2214 reader.seek(SeekFrom::Start(model_view_offset as u64))?;
2216 let mut model_view = vec![0u8; MODEL_VIEW_SIZE];
2217 reader.read_exact(&mut model_view)?;
2218
2219 let n_indices =
2222 u32::from_le_bytes([model_view[0], model_view[1], model_view[2], model_view[3]]);
2223 let ofs_indices =
2224 u32::from_le_bytes([model_view[4], model_view[5], model_view[6], model_view[7]]);
2225
2226 let n_triangles =
2227 u32::from_le_bytes([model_view[8], model_view[9], model_view[10], model_view[11]]);
2228 let ofs_triangles = u32::from_le_bytes([
2229 model_view[12],
2230 model_view[13],
2231 model_view[14],
2232 model_view[15],
2233 ]);
2234
2235 let n_properties = u32::from_le_bytes([
2236 model_view[16],
2237 model_view[17],
2238 model_view[18],
2239 model_view[19],
2240 ]);
2241 let ofs_properties = u32::from_le_bytes([
2242 model_view[20],
2243 model_view[21],
2244 model_view[22],
2245 model_view[23],
2246 ]);
2247
2248 let n_submeshes = u32::from_le_bytes([
2249 model_view[24],
2250 model_view[25],
2251 model_view[26],
2252 model_view[27],
2253 ]);
2254 let ofs_submeshes = u32::from_le_bytes([
2255 model_view[28],
2256 model_view[29],
2257 model_view[30],
2258 model_view[31],
2259 ]);
2260
2261 let n_batches = u32::from_le_bytes([
2262 model_view[32],
2263 model_view[33],
2264 model_view[34],
2265 model_view[35],
2266 ]);
2267 let ofs_batches = u32::from_le_bytes([
2268 model_view[36],
2269 model_view[37],
2270 model_view[38],
2271 model_view[39],
2272 ]);
2273
2274 let indices = if n_indices > 0 && ofs_indices > 0 {
2276 reader.seek(SeekFrom::Start(ofs_indices as u64))?;
2277 let mut data = vec![0u8; n_indices as usize * 2];
2278 reader.read_exact(&mut data)?;
2279 data
2280 } else {
2281 Vec::new()
2282 };
2283
2284 let triangles = if n_triangles > 0 && ofs_triangles > 0 {
2286 reader.seek(SeekFrom::Start(ofs_triangles as u64))?;
2287 let mut data = vec![0u8; n_triangles as usize * 2];
2288 reader.read_exact(&mut data)?;
2289 data
2290 } else {
2291 Vec::new()
2292 };
2293
2294 let properties = if n_properties > 0 && ofs_properties > 0 {
2296 reader.seek(SeekFrom::Start(ofs_properties as u64))?;
2297 let mut data = vec![0u8; n_properties as usize * 4];
2299 reader.read_exact(&mut data)?;
2300 data
2301 } else {
2302 Vec::new()
2303 };
2304
2305 let submeshes = if n_submeshes > 0 && ofs_submeshes > 0 {
2307 reader.seek(SeekFrom::Start(ofs_submeshes as u64))?;
2308 let mut data = vec![0u8; n_submeshes as usize * submesh_size];
2309 reader.read_exact(&mut data)?;
2310 data
2311 } else {
2312 Vec::new()
2313 };
2314
2315 let batches = if n_batches > 0 && ofs_batches > 0 {
2318 reader.seek(SeekFrom::Start(ofs_batches as u64))?;
2319 let mut data = vec![0u8; n_batches as usize * 24];
2320 reader.read_exact(&mut data)?;
2321 data
2322 } else {
2323 Vec::new()
2324 };
2325
2326 embedded_skins.push(EmbeddedSkinRaw {
2327 model_view,
2328 indices,
2329 triangles,
2330 properties,
2331 submeshes,
2332 batches,
2333 original_model_view_offset: model_view_offset,
2334 original_indices_offset: ofs_indices,
2335 original_triangles_offset: ofs_triangles,
2336 original_properties_offset: ofs_properties,
2337 original_submeshes_offset: ofs_submeshes,
2338 original_batches_offset: ofs_batches,
2339 });
2340 }
2341
2342 Ok(embedded_skins)
2343}
2344
2345impl M2Format {
2346 pub fn model(&self) -> &M2Model {
2348 match self {
2349 M2Format::Legacy(model) => model,
2350 M2Format::Chunked(model) => model,
2351 }
2352 }
2353
2354 pub fn model_mut(&mut self) -> &mut M2Model {
2356 match self {
2357 M2Format::Legacy(model) => model,
2358 M2Format::Chunked(model) => model,
2359 }
2360 }
2361
2362 pub fn is_chunked(&self) -> bool {
2364 matches!(self, M2Format::Chunked(_))
2365 }
2366
2367 pub fn is_legacy(&self) -> bool {
2369 matches!(self, M2Format::Legacy(_))
2370 }
2371}
2372
2373impl Default for M2Model {
2374 fn default() -> Self {
2375 Self {
2376 header: M2Header {
2377 magic: *b"MD20",
2378 version: 264, name: M2Array::default(),
2380 flags: M2ModelFlags::empty(),
2381 global_sequences: M2Array::default(),
2382 animations: M2Array::default(),
2383 animation_lookup: M2Array::default(),
2384 playable_animation_lookup: None,
2385 bones: M2Array::default(),
2386 key_bone_lookup: M2Array::default(),
2387 vertices: M2Array::default(),
2388 views: M2Array::default(),
2389 num_skin_profiles: Some(0),
2390 color_animations: M2Array::default(),
2391 textures: M2Array::default(),
2392 transparency_lookup: M2Array::default(),
2393 texture_flipbooks: None,
2394 texture_animations: M2Array::default(),
2395 color_replacements: M2Array::default(),
2396 render_flags: M2Array::default(),
2397 bone_lookup_table: M2Array::default(),
2398 texture_lookup_table: M2Array::default(),
2399 texture_units: M2Array::default(),
2400 transparency_lookup_table: M2Array::default(),
2401 texture_animation_lookup: M2Array::default(),
2402 bounding_box_min: [0.0, 0.0, 0.0],
2403 bounding_box_max: [0.0, 0.0, 0.0],
2404 bounding_sphere_radius: 0.0,
2405 collision_box_min: [0.0, 0.0, 0.0],
2406 collision_box_max: [0.0, 0.0, 0.0],
2407 collision_sphere_radius: 0.0,
2408 bounding_triangles: M2Array::default(),
2409 bounding_vertices: M2Array::default(),
2410 bounding_normals: M2Array::default(),
2411 attachments: M2Array::default(),
2412 attachment_lookup_table: M2Array::default(),
2413 events: M2Array::default(),
2414 lights: M2Array::default(),
2415 cameras: M2Array::default(),
2416 camera_lookup_table: M2Array::default(),
2417 ribbon_emitters: M2Array::default(),
2418 particle_emitters: M2Array::default(),
2419 blend_map_overrides: None,
2420 texture_combiner_combos: None,
2421 texture_transforms: None,
2422 },
2423 name: None,
2424 global_sequences: Vec::new(),
2425 animations: Vec::new(),
2426 animation_lookup: Vec::new(),
2427 bones: Vec::new(),
2428 key_bone_lookup: Vec::new(),
2429 vertices: Vec::new(),
2430 textures: Vec::new(),
2431 materials: Vec::new(),
2432 particle_emitters: Vec::new(),
2433 ribbon_emitters: Vec::new(),
2434 texture_animations: Vec::new(),
2435 color_animations: Vec::new(),
2436 transparency_animations: Vec::new(),
2437 events: Vec::new(),
2438 attachments: Vec::new(),
2439 cameras: Vec::new(),
2440 lights: Vec::new(),
2441 raw_data: M2RawData::default(),
2442 skin_file_ids: None,
2443 animation_file_ids: None,
2444 texture_file_ids: None,
2445 physics_file_id: None,
2446 skeleton_file_id: None,
2447 bone_file_ids: None,
2448 lod_data: None,
2449 extended_particle_data: None,
2450 parent_animation_blacklist: None,
2451 parent_animation_data: None,
2452 waterfall_effect: None,
2453 edge_fade_data: None,
2454 model_alpha_data: None,
2455 lighting_details: None,
2456 recursive_particle_ids: None,
2457 geometry_particle_ids: None,
2458 texture_animation_chunk: None,
2459 particle_geoset_data: None,
2460 dboc_chunk: None,
2461 afra_chunk: None,
2462 dpiv_chunk: None,
2463 parent_sequence_bounds: None,
2464 parent_event_data: None,
2465 collision_mesh_data: None,
2466 physics_file_data: None,
2467 }
2468 }
2469}
2470
2471impl M2Model {
2472 pub fn parse_legacy<R: Read + Seek>(reader: &mut R) -> Result<Self> {
2474 Self::parse(reader)
2475 }
2476
2477 pub fn parse_chunked<R: Read + Seek>(reader: &mut R) -> Result<Self> {
2479 let mut chunks = Vec::new();
2480 let mut md21_chunk = None;
2481 let mut skin_file_ids = None;
2482 let mut animation_file_ids = None;
2483 let mut texture_file_ids = None;
2484 let mut physics_file_id = None;
2485 let mut skeleton_file_id = None;
2486 let mut bone_file_ids = None;
2487 let mut lod_data = None;
2488 let mut extended_particle_data = None;
2489 let mut parent_animation_blacklist = None;
2490 let mut parent_animation_data = None;
2491 let mut waterfall_effect = None;
2492 let mut edge_fade_data = None;
2493 let mut model_alpha_data = None;
2494 let mut lighting_details = None;
2495 let mut recursive_particle_ids = None;
2496 let mut geometry_particle_ids = None;
2497 let mut texture_animation_chunk = None;
2498 let mut particle_geoset_data = None;
2499 let mut dboc_chunk = None;
2500 let mut afra_chunk = None;
2501 let mut dpiv_chunk = None;
2502 let mut parent_sequence_bounds = None;
2503 let mut parent_event_data = None;
2504 let mut collision_mesh_data = None;
2505 let mut physics_file_data = None;
2506
2507 loop {
2509 let header = match ChunkHeader::read(reader) {
2510 Ok(h) => h,
2511 Err(M2Error::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => break,
2512 Err(e) => return Err(e),
2513 };
2514
2515 chunks.push(header.clone());
2516
2517 match &header.magic {
2518 b"MD21" => {
2519 let current_pos = reader.stream_position()?;
2522
2523 reader.seek(SeekFrom::Current(header.size as i64))?;
2525
2526 md21_chunk = Some(Self::parse_md21_simple(current_pos, &header)?);
2529 }
2530 b"SFID" => {
2531 let current_pos = reader.stream_position()?;
2533 let end_pos = current_pos + header.size as u64;
2534
2535 let count = header.size / 4; let mut ids = Vec::with_capacity(count as usize);
2537
2538 for _ in 0..count {
2539 ids.push(reader.read_u32_le()?);
2540 }
2541
2542 skin_file_ids = Some(SkinFileIds { ids });
2543
2544 reader.seek(SeekFrom::Start(end_pos))?;
2546 }
2547 b"AFID" => {
2548 let current_pos = reader.stream_position()?;
2550 let end_pos = current_pos + header.size as u64;
2551
2552 let count = header.size / 4; let mut ids = Vec::with_capacity(count as usize);
2554
2555 for _ in 0..count {
2556 ids.push(reader.read_u32_le()?);
2557 }
2558
2559 animation_file_ids = Some(AnimationFileIds { ids });
2560
2561 reader.seek(SeekFrom::Start(end_pos))?;
2563 }
2564 b"TXID" => {
2565 let current_pos = reader.stream_position()?;
2567 let end_pos = current_pos + header.size as u64;
2568
2569 let count = header.size / 4; let mut ids = Vec::with_capacity(count as usize);
2571
2572 for _ in 0..count {
2573 ids.push(reader.read_u32_le()?);
2574 }
2575
2576 texture_file_ids = Some(TextureFileIds { ids });
2577
2578 reader.seek(SeekFrom::Start(end_pos))?;
2580 }
2581 b"PFID" => {
2582 if header.size != 4 {
2584 return Err(M2Error::ParseError(format!(
2585 "PFID chunk should contain exactly 4 bytes, got {}",
2586 header.size
2587 )));
2588 }
2589
2590 let id = reader.read_u32_le()?;
2591 physics_file_id = Some(PhysicsFileId { id });
2592 }
2593 b"SKID" => {
2594 if header.size != 4 {
2596 return Err(M2Error::ParseError(format!(
2597 "SKID chunk should contain exactly 4 bytes, got {}",
2598 header.size
2599 )));
2600 }
2601
2602 let id = reader.read_u32_le()?;
2603 skeleton_file_id = Some(SkeletonFileId { id });
2604 }
2605 b"BFID" => {
2606 let current_pos = reader.stream_position()?;
2608 let end_pos = current_pos + header.size as u64;
2609
2610 let count = header.size / 4; let mut ids = Vec::with_capacity(count as usize);
2612
2613 for _ in 0..count {
2614 ids.push(reader.read_u32_le()?);
2615 }
2616
2617 bone_file_ids = Some(BoneFileIds { ids });
2618
2619 reader.seek(SeekFrom::Start(end_pos))?;
2621 }
2622 b"LDV1" => {
2623 let current_pos = reader.stream_position()?;
2625 let end_pos = current_pos + header.size as u64;
2626
2627 const LOD_LEVEL_SIZE: u32 = 14;
2629
2630 if header.size % LOD_LEVEL_SIZE != 0 {
2631 return Err(M2Error::ParseError(format!(
2632 "LDV1 chunk size {} is not a multiple of LOD level size {}",
2633 header.size, LOD_LEVEL_SIZE
2634 )));
2635 }
2636
2637 let count = header.size / LOD_LEVEL_SIZE;
2638 let mut levels = Vec::with_capacity(count as usize);
2639
2640 for _ in 0..count {
2641 use crate::chunks::file_references::LodLevel;
2642
2643 let distance = reader.read_f32_le()?;
2644 let skin_file_index = reader.read_u16_le()?;
2645 let vertex_count = reader.read_u32_le()?;
2646 let triangle_count = reader.read_u32_le()?;
2647
2648 levels.push(LodLevel {
2649 distance,
2650 skin_file_index,
2651 vertex_count,
2652 triangle_count,
2653 });
2654 }
2655
2656 lod_data = Some(LodData { levels });
2657
2658 reader.seek(SeekFrom::Start(end_pos))?;
2660 }
2661 b"EXPT" => {
2662 let current_pos = reader.stream_position()?;
2664 let _end_pos = current_pos + header.size as u64;
2665
2666 let mut chunk_data = vec![0u8; header.size as usize];
2668 reader.read_exact(&mut chunk_data)?;
2669 let chunk_cursor = std::io::Cursor::new(chunk_data);
2670 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2671
2672 extended_particle_data =
2673 Some(ExtendedParticleData::parse_expt(&mut chunk_reader)?);
2674
2675 }
2677 b"EXP2" => {
2678 let current_pos = reader.stream_position()?;
2680 let _end_pos = current_pos + header.size as u64;
2681
2682 let mut chunk_data = vec![0u8; header.size as usize];
2684 reader.read_exact(&mut chunk_data)?;
2685 let chunk_cursor = std::io::Cursor::new(chunk_data);
2686 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2687
2688 extended_particle_data =
2689 Some(ExtendedParticleData::parse_exp2(&mut chunk_reader)?);
2690
2691 }
2693 b"PABC" => {
2694 let current_pos = reader.stream_position()?;
2696 let _end_pos = current_pos + header.size as u64;
2697
2698 let mut chunk_data = vec![0u8; header.size as usize];
2700 reader.read_exact(&mut chunk_data)?;
2701 let chunk_cursor = std::io::Cursor::new(chunk_data);
2702 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2703
2704 parent_animation_blacklist =
2705 Some(ParentAnimationBlacklist::parse(&mut chunk_reader)?);
2706
2707 }
2709 b"PADC" => {
2710 let current_pos = reader.stream_position()?;
2712 let _end_pos = current_pos + header.size as u64;
2713
2714 let mut chunk_data = vec![0u8; header.size as usize];
2716 reader.read_exact(&mut chunk_data)?;
2717 let chunk_cursor = std::io::Cursor::new(chunk_data);
2718 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2719
2720 parent_animation_data = Some(ParentAnimationData::parse(&mut chunk_reader)?);
2721
2722 }
2724 b"WFV1" => {
2725 let current_pos = reader.stream_position()?;
2727 let _end_pos = current_pos + header.size as u64;
2728
2729 let mut chunk_data = vec![0u8; header.size as usize];
2731 reader.read_exact(&mut chunk_data)?;
2732 let chunk_cursor = std::io::Cursor::new(chunk_data);
2733 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2734
2735 waterfall_effect = Some(WaterfallEffect::parse(&mut chunk_reader, 1)?);
2736
2737 }
2739 b"WFV2" => {
2740 let current_pos = reader.stream_position()?;
2742 let _end_pos = current_pos + header.size as u64;
2743
2744 let mut chunk_data = vec![0u8; header.size as usize];
2746 reader.read_exact(&mut chunk_data)?;
2747 let chunk_cursor = std::io::Cursor::new(chunk_data);
2748 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2749
2750 waterfall_effect = Some(WaterfallEffect::parse(&mut chunk_reader, 2)?);
2751
2752 }
2754 b"WFV3" => {
2755 let current_pos = reader.stream_position()?;
2757 let _end_pos = current_pos + header.size as u64;
2758
2759 let mut chunk_data = vec![0u8; header.size as usize];
2761 reader.read_exact(&mut chunk_data)?;
2762 let chunk_cursor = std::io::Cursor::new(chunk_data);
2763 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2764
2765 waterfall_effect = Some(WaterfallEffect::parse(&mut chunk_reader, 3)?);
2766
2767 }
2769 b"EDGF" => {
2770 let current_pos = reader.stream_position()?;
2772 let _end_pos = current_pos + header.size as u64;
2773
2774 let mut chunk_data = vec![0u8; header.size as usize];
2776 reader.read_exact(&mut chunk_data)?;
2777 let chunk_cursor = std::io::Cursor::new(chunk_data);
2778 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2779
2780 edge_fade_data = Some(EdgeFadeData::parse(&mut chunk_reader)?);
2781
2782 }
2784 b"NERF" => {
2785 let current_pos = reader.stream_position()?;
2787 let _end_pos = current_pos + header.size as u64;
2788
2789 let mut chunk_data = vec![0u8; header.size as usize];
2791 reader.read_exact(&mut chunk_data)?;
2792 let chunk_cursor = std::io::Cursor::new(chunk_data);
2793 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2794
2795 model_alpha_data = Some(ModelAlphaData::parse(&mut chunk_reader)?);
2796
2797 }
2799 b"DETL" => {
2800 let current_pos = reader.stream_position()?;
2802 let _end_pos = current_pos + header.size as u64;
2803
2804 let mut chunk_data = vec![0u8; header.size as usize];
2806 reader.read_exact(&mut chunk_data)?;
2807 let chunk_cursor = std::io::Cursor::new(chunk_data);
2808 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2809
2810 lighting_details = Some(LightingDetails::parse(&mut chunk_reader)?);
2811
2812 }
2814 b"RPID" => {
2815 let current_pos = reader.stream_position()?;
2817 let _end_pos = current_pos + header.size as u64;
2818
2819 let mut chunk_data = vec![0u8; header.size as usize];
2821 reader.read_exact(&mut chunk_data)?;
2822 let chunk_cursor = std::io::Cursor::new(chunk_data);
2823 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2824
2825 recursive_particle_ids = Some(RecursiveParticleIds::parse(&mut chunk_reader)?);
2826
2827 }
2829 b"GPID" => {
2830 let current_pos = reader.stream_position()?;
2832 let _end_pos = current_pos + header.size as u64;
2833
2834 let mut chunk_data = vec![0u8; header.size as usize];
2836 reader.read_exact(&mut chunk_data)?;
2837 let chunk_cursor = std::io::Cursor::new(chunk_data);
2838 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2839
2840 geometry_particle_ids = Some(GeometryParticleIds::parse(&mut chunk_reader)?);
2841
2842 }
2844 b"TXAC" => {
2845 let current_pos = reader.stream_position()?;
2847 let _end_pos = current_pos + header.size as u64;
2848
2849 let mut chunk_data = vec![0u8; header.size as usize];
2851 reader.read_exact(&mut chunk_data)?;
2852 let chunk_cursor = std::io::Cursor::new(chunk_data);
2853 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2854
2855 texture_animation_chunk =
2856 Some(TextureAnimationChunk::parse(&mut chunk_reader)?);
2857
2858 }
2860 b"PGD1" => {
2861 let current_pos = reader.stream_position()?;
2863 let _end_pos = current_pos + header.size as u64;
2864
2865 let mut chunk_data = vec![0u8; header.size as usize];
2867 reader.read_exact(&mut chunk_data)?;
2868 let chunk_cursor = std::io::Cursor::new(chunk_data);
2869 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2870
2871 particle_geoset_data = Some(ParticleGeosetData::parse(&mut chunk_reader)?);
2872
2873 }
2875 b"DBOC" => {
2876 let current_pos = reader.stream_position()?;
2878 let _end_pos = current_pos + header.size as u64;
2879
2880 let mut chunk_data = vec![0u8; header.size as usize];
2882 reader.read_exact(&mut chunk_data)?;
2883 let chunk_cursor = std::io::Cursor::new(chunk_data);
2884 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2885
2886 dboc_chunk = Some(DbocChunk::parse(&mut chunk_reader)?);
2887
2888 }
2890 b"AFRA" => {
2891 let current_pos = reader.stream_position()?;
2893 let _end_pos = current_pos + header.size as u64;
2894
2895 let mut chunk_data = vec![0u8; header.size as usize];
2897 reader.read_exact(&mut chunk_data)?;
2898 let chunk_cursor = std::io::Cursor::new(chunk_data);
2899 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2900
2901 afra_chunk = Some(AfraChunk::parse(&mut chunk_reader)?);
2902
2903 }
2905 b"DPIV" => {
2906 let current_pos = reader.stream_position()?;
2908 let _end_pos = current_pos + header.size as u64;
2909
2910 let mut chunk_data = vec![0u8; header.size as usize];
2912 reader.read_exact(&mut chunk_data)?;
2913 let chunk_cursor = std::io::Cursor::new(chunk_data);
2914 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2915
2916 dpiv_chunk = Some(DpivChunk::parse(&mut chunk_reader)?);
2917
2918 }
2920 b"PSBC" => {
2921 let current_pos = reader.stream_position()?;
2923 let _end_pos = current_pos + header.size as u64;
2924
2925 let mut chunk_data = vec![0u8; header.size as usize];
2927 reader.read_exact(&mut chunk_data)?;
2928 let chunk_cursor = std::io::Cursor::new(chunk_data);
2929 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2930
2931 parent_sequence_bounds = Some(ParentSequenceBounds::parse(&mut chunk_reader)?);
2932
2933 }
2935 b"PEDC" => {
2936 let current_pos = reader.stream_position()?;
2938 let _end_pos = current_pos + header.size as u64;
2939
2940 let mut chunk_data = vec![0u8; header.size as usize];
2942 reader.read_exact(&mut chunk_data)?;
2943 let chunk_cursor = std::io::Cursor::new(chunk_data);
2944 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2945
2946 parent_event_data = Some(ParentEventData::parse(&mut chunk_reader)?);
2947
2948 }
2950 b"PCOL" => {
2951 let current_pos = reader.stream_position()?;
2953 let _end_pos = current_pos + header.size as u64;
2954
2955 let mut chunk_data = vec![0u8; header.size as usize];
2957 reader.read_exact(&mut chunk_data)?;
2958 let chunk_cursor = std::io::Cursor::new(chunk_data);
2959 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2960
2961 collision_mesh_data = Some(CollisionMeshData::parse(&mut chunk_reader)?);
2962
2963 }
2965 b"PFDC" => {
2966 let current_pos = reader.stream_position()?;
2968 let _end_pos = current_pos + header.size as u64;
2969
2970 let mut chunk_data = vec![0u8; header.size as usize];
2972 reader.read_exact(&mut chunk_data)?;
2973 let chunk_cursor = std::io::Cursor::new(chunk_data);
2974 let mut chunk_reader = ChunkReader::new(chunk_cursor, header.clone())?;
2975
2976 physics_file_data = Some(PhysicsFileDataChunk::parse(&mut chunk_reader)?);
2977
2978 }
2980 _ => {
2981 reader.seek(SeekFrom::Current(header.size as i64))?;
2983 }
2984 }
2985 }
2986
2987 let mut model = md21_chunk.ok_or(M2Error::MissingMD21Chunk)?;
2989
2990 model.skin_file_ids = skin_file_ids;
2992 model.animation_file_ids = animation_file_ids;
2993 model.texture_file_ids = texture_file_ids;
2994 model.physics_file_id = physics_file_id;
2995 model.skeleton_file_id = skeleton_file_id;
2996 model.bone_file_ids = bone_file_ids;
2997 model.lod_data = lod_data;
2998 model.extended_particle_data = extended_particle_data;
2999 model.parent_animation_blacklist = parent_animation_blacklist;
3000 model.parent_animation_data = parent_animation_data;
3001 model.waterfall_effect = waterfall_effect;
3002 model.edge_fade_data = edge_fade_data;
3003 model.model_alpha_data = model_alpha_data;
3004 model.lighting_details = lighting_details;
3005 model.recursive_particle_ids = recursive_particle_ids;
3006 model.geometry_particle_ids = geometry_particle_ids;
3007 model.texture_animation_chunk = texture_animation_chunk;
3008 model.particle_geoset_data = particle_geoset_data;
3009 model.dboc_chunk = dboc_chunk;
3010 model.afra_chunk = afra_chunk;
3011 model.dpiv_chunk = dpiv_chunk;
3012 model.parent_sequence_bounds = parent_sequence_bounds;
3013 model.parent_event_data = parent_event_data;
3014 model.collision_mesh_data = collision_mesh_data;
3015 model.physics_file_data = physics_file_data;
3016
3017 Ok(model)
3018 }
3019
3020 fn parse_md21_simple(_chunk_pos: u64, _header: &ChunkHeader) -> Result<Self> {
3022 Ok(Self {
3025 header: M2Header::new(M2Version::Legion),
3026 name: None,
3027 global_sequences: Vec::new(),
3028 animations: Vec::new(),
3029 animation_lookup: Vec::new(),
3030 bones: Vec::new(),
3031 key_bone_lookup: Vec::new(),
3032 vertices: Vec::new(),
3033 textures: Vec::new(),
3034 materials: Vec::new(),
3035 particle_emitters: Vec::new(),
3036 ribbon_emitters: Vec::new(),
3037 texture_animations: Vec::new(),
3038 color_animations: Vec::new(),
3039 transparency_animations: Vec::new(),
3040 events: Vec::new(),
3041 attachments: Vec::new(),
3042 cameras: Vec::new(),
3043 lights: Vec::new(),
3044 raw_data: M2RawData::default(),
3045 skin_file_ids: None,
3046 animation_file_ids: None,
3047 texture_file_ids: None,
3048 physics_file_id: None,
3049 skeleton_file_id: None,
3050 bone_file_ids: None,
3051 lod_data: None,
3052 extended_particle_data: None,
3053 parent_animation_blacklist: None,
3054 parent_animation_data: None,
3055 waterfall_effect: None,
3056 edge_fade_data: None,
3057 model_alpha_data: None,
3058 lighting_details: None,
3059 recursive_particle_ids: None,
3060 geometry_particle_ids: None,
3061 texture_animation_chunk: None,
3062 particle_geoset_data: None,
3063 dboc_chunk: None,
3064 afra_chunk: None,
3065 dpiv_chunk: None,
3066 parent_sequence_bounds: None,
3067 parent_event_data: None,
3068 collision_mesh_data: None,
3069 physics_file_data: None,
3070 })
3071 }
3072
3073 fn _parse_md21_chunk<R: Read + Seek>(mut reader: ChunkReader<R>) -> Result<Self> {
3075 let chunk_inner = reader.inner();
3081
3082 let mut magic = [0u8; 4];
3084 chunk_inner.read_exact(&mut magic)?;
3085
3086 if magic != M2_MAGIC_LEGACY {
3087 return Err(M2Error::InvalidMagic {
3088 expected: String::from_utf8_lossy(&M2_MAGIC_LEGACY).to_string(),
3089 actual: String::from_utf8_lossy(&magic).to_string(),
3090 });
3091 }
3092
3093 let version = chunk_inner.read_u32_le()?;
3095
3096 if M2Version::from_header_version(version).is_none() {
3098 return Err(M2Error::UnsupportedVersion(version.to_string()));
3099 }
3100
3101 let name = M2Array::parse(chunk_inner)?;
3104 let flags = M2ModelFlags::from_bits_retain(chunk_inner.read_u32_le()?);
3105
3106 let global_sequences = M2Array::parse(chunk_inner)?;
3107 let animations = M2Array::parse(chunk_inner)?;
3108 let animation_lookup = M2Array::parse(chunk_inner)?;
3109
3110 let playable_animation_lookup = if version <= 263 {
3112 Some(M2Array::parse(chunk_inner)?)
3113 } else {
3114 None
3115 };
3116
3117 let bones = M2Array::parse(chunk_inner)?;
3118 let key_bone_lookup = M2Array::parse(chunk_inner)?;
3119
3120 let vertices = M2Array::parse(chunk_inner)?;
3121
3122 let (views, num_skin_profiles) = if version <= 263 {
3124 (M2Array::parse(chunk_inner)?, None)
3126 } else {
3127 let count = chunk_inner.read_u32_le()?;
3129 (M2Array::new(0, 0), Some(count))
3130 };
3131
3132 let color_animations = M2Array::parse(chunk_inner)?;
3133
3134 let textures = M2Array::parse(chunk_inner)?;
3135 let transparency_lookup = M2Array::parse(chunk_inner)?;
3136
3137 let texture_flipbooks = if version <= 263 {
3139 Some(M2Array::parse(chunk_inner)?)
3140 } else {
3141 None
3142 };
3143
3144 let texture_animations = M2Array::parse(chunk_inner)?;
3145
3146 let color_replacements = M2Array::parse(chunk_inner)?;
3147 let render_flags = M2Array::parse(chunk_inner)?;
3148 let bone_lookup_table = M2Array::parse(chunk_inner)?;
3149 let texture_lookup_table = M2Array::parse(chunk_inner)?;
3150 let texture_units = M2Array::parse(chunk_inner)?;
3151 let transparency_lookup_table = M2Array::parse(chunk_inner)?;
3152 let mut texture_animation_lookup = M2Array::parse(chunk_inner)?;
3153
3154 if texture_animation_lookup.count > 1_000_000 {
3156 texture_animation_lookup = M2Array::new(0, 0);
3157 }
3158
3159 let mut bounding_box_min = [0.0; 3];
3161 let mut bounding_box_max = [0.0; 3];
3162
3163 for item in &mut bounding_box_min {
3164 *item = chunk_inner.read_f32_le()?;
3165 }
3166
3167 for item in &mut bounding_box_max {
3168 *item = chunk_inner.read_f32_le()?;
3169 }
3170
3171 let bounding_sphere_radius = chunk_inner.read_f32_le()?;
3172
3173 let mut collision_box_min = [0.0; 3];
3175 let mut collision_box_max = [0.0; 3];
3176
3177 for item in &mut collision_box_min {
3178 *item = chunk_inner.read_f32_le()?;
3179 }
3180
3181 for item in &mut collision_box_max {
3182 *item = chunk_inner.read_f32_le()?;
3183 }
3184
3185 let collision_sphere_radius = chunk_inner.read_f32_le()?;
3186
3187 let bounding_triangles = M2Array::parse(chunk_inner)?;
3188 let bounding_vertices = M2Array::parse(chunk_inner)?;
3189 let bounding_normals = M2Array::parse(chunk_inner)?;
3190
3191 let attachments = M2Array::parse(chunk_inner)?;
3192 let attachment_lookup_table = M2Array::parse(chunk_inner)?;
3193 let events = M2Array::parse(chunk_inner)?;
3194 let lights = M2Array::parse(chunk_inner)?;
3195 let cameras = M2Array::parse(chunk_inner)?;
3196 let camera_lookup_table = M2Array::parse(chunk_inner)?;
3197
3198 let ribbon_emitters = M2Array::parse(chunk_inner)?;
3199 let particle_emitters = M2Array::parse(chunk_inner)?;
3200
3201 let m2_version = M2Version::from_header_version(version).unwrap();
3203
3204 let blend_map_overrides = if version >= 260 && (flags.bits() & 0x8000000 != 0) {
3205 Some(M2Array::parse(chunk_inner)?)
3206 } else {
3207 None
3208 };
3209
3210 let texture_combiner_combos = if m2_version >= M2Version::Cataclysm {
3211 Some(M2Array::parse(chunk_inner)?)
3212 } else {
3213 None
3214 };
3215
3216 let texture_transforms = if m2_version >= M2Version::Legion {
3217 Some(M2Array::parse(chunk_inner)?)
3218 } else {
3219 None
3220 };
3221
3222 let header = M2Header {
3224 magic: M2_MAGIC_LEGACY,
3225 version,
3226 name,
3227 flags,
3228 global_sequences,
3229 animations,
3230 animation_lookup,
3231 playable_animation_lookup,
3232 bones,
3233 key_bone_lookup,
3234 vertices,
3235 views,
3236 num_skin_profiles,
3237 color_animations,
3238 textures,
3239 transparency_lookup,
3240 texture_flipbooks,
3241 texture_animations,
3242 color_replacements,
3243 render_flags,
3244 bone_lookup_table,
3245 texture_lookup_table,
3246 texture_units,
3247 transparency_lookup_table,
3248 texture_animation_lookup,
3249 bounding_box_min,
3250 bounding_box_max,
3251 bounding_sphere_radius,
3252 collision_box_min,
3253 collision_box_max,
3254 collision_sphere_radius,
3255 bounding_triangles,
3256 bounding_vertices,
3257 bounding_normals,
3258 attachments,
3259 attachment_lookup_table,
3260 events,
3261 lights,
3262 cameras,
3263 camera_lookup_table,
3264 ribbon_emitters,
3265 particle_emitters,
3266 blend_map_overrides,
3267 texture_combiner_combos,
3268 texture_transforms,
3269 };
3270
3271 Ok(Self {
3279 header,
3280 name: None, global_sequences: Vec::new(), animations: Vec::new(), animation_lookup: Vec::new(), bones: Vec::new(), key_bone_lookup: Vec::new(), vertices: Vec::new(), textures: Vec::new(), materials: Vec::new(), particle_emitters: Vec::new(), ribbon_emitters: Vec::new(), texture_animations: Vec::new(), color_animations: Vec::new(), transparency_animations: Vec::new(), events: Vec::new(), attachments: Vec::new(),
3296 cameras: Vec::new(),
3297 lights: Vec::new(), raw_data: M2RawData::default(),
3299 skin_file_ids: None, animation_file_ids: None, texture_file_ids: None, physics_file_id: None, skeleton_file_id: None, bone_file_ids: None, lod_data: None, extended_particle_data: None, parent_animation_blacklist: None, parent_animation_data: None, waterfall_effect: None, edge_fade_data: None, model_alpha_data: None, lighting_details: None, recursive_particle_ids: None, geometry_particle_ids: None, texture_animation_chunk: None, particle_geoset_data: None, dboc_chunk: None, afra_chunk: None, dpiv_chunk: None, parent_sequence_bounds: None, parent_event_data: None, collision_mesh_data: None, physics_file_data: None, })
3325 }
3326
3327 pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
3329 let header = M2Header::parse(reader)?;
3331
3332 let _version = header
3334 .version()
3335 .ok_or(M2Error::UnsupportedVersion(header.version.to_string()))?;
3336
3337 let name = if header.name.count > 0 {
3339 reader.seek(SeekFrom::Start(header.name.offset as u64))?;
3341
3342 let name_bytes = read_array(reader, &header.name, |r| Ok(r.read_u8()?))?;
3344
3345 let name_end = name_bytes
3347 .iter()
3348 .position(|&b| b == 0)
3349 .unwrap_or(name_bytes.len());
3350 let name_str = String::from_utf8_lossy(&name_bytes[..name_end]).to_string();
3351 Some(name_str)
3352 } else {
3353 None
3354 };
3355
3356 let global_sequences =
3358 read_array(reader, &header.global_sequences, |r| Ok(r.read_u32_le()?))?;
3359
3360 let animations = read_array(reader, &header.animations.convert(), |r| {
3362 M2Animation::parse(r, header.version)
3363 })?;
3364
3365 let animation_lookup =
3367 read_array(reader, &header.animation_lookup, |r| Ok(r.read_u16_le()?))?;
3368
3369 let bones = if header.version == 260 && header.bones.count == 203 {
3372 let current_pos = reader.stream_position()?;
3374 let file_size = reader.seek(SeekFrom::End(0))?;
3375 reader.seek(SeekFrom::Start(current_pos))?; let bone_size = 92; let expected_end = header.bones.offset as u64 + (header.bones.count as u64 * bone_size);
3379
3380 if expected_end > file_size {
3381 Vec::new()
3386 } else {
3387 read_array(reader, &header.bones.convert(), |r| {
3389 M2Bone::parse(r, header.version)
3390 })?
3391 }
3392 } else {
3393 read_array(reader, &header.bones.convert(), |r| {
3395 M2Bone::parse(r, header.version)
3396 })?
3397 };
3398
3399 let key_bone_lookup =
3401 read_array(reader, &header.key_bone_lookup, |r| Ok(r.read_u16_le()?))?;
3402
3403 let bone_count = header.bones.count;
3405 let vertices = read_array(reader, &header.vertices.convert(), |r| {
3406 M2Vertex::parse_with_validation(
3408 r,
3409 header.version,
3410 Some(bone_count),
3411 crate::chunks::vertex::ValidationMode::default(),
3412 )
3413 })?;
3414
3415 let textures = read_array(reader, &header.textures.convert(), |r| {
3417 M2Texture::parse(r, header.version)
3418 })?;
3419
3420 let materials = read_array(reader, &header.render_flags.convert(), |r| {
3422 M2Material::parse(r, header.version)
3423 })?;
3424
3425 let particle_emitters = read_array(reader, &header.particle_emitters.convert(), |r| {
3427 M2ParticleEmitter::parse(r, header.version)
3428 })?;
3429
3430 let ribbon_emitters = read_array(reader, &header.ribbon_emitters.convert(), |r| {
3432 M2RibbonEmitter::parse(r, header.version)
3433 })?;
3434
3435 let texture_animations = read_array(reader, &header.texture_animations.convert(), |r| {
3437 M2TextureAnimation::parse(r)
3438 })?;
3439
3440 let color_animations = read_array(reader, &header.color_animations.convert(), |r| {
3442 M2ColorAnimation::parse(r)
3443 })?;
3444
3445 let transparency_animations =
3448 read_array(reader, &header.transparency_lookup.convert(), |r| {
3449 M2TransparencyAnimation::parse(r)
3450 })?;
3451
3452 let events = read_array(reader, &header.events.convert(), |r| {
3454 M2Event::parse(r, header.version)
3455 })?;
3456
3457 let attachments = read_array(reader, &header.attachments.convert(), |r| {
3459 M2Attachment::parse(r, header.version)
3460 })?;
3461
3462 let cameras = read_array(reader, &header.cameras.convert(), |r| {
3464 M2Camera::parse(r, header.version)
3465 })?;
3466
3467 let lights = read_array(reader, &header.lights.convert(), |r| {
3469 M2Light::parse(r, header.version)
3470 })?;
3471
3472 let bone_animation_data = collect_bone_animation_data(reader, &bones, header.version)?;
3474
3475 let particle_animation_data = collect_particle_animation_data(reader, &particle_emitters)?;
3477
3478 let ribbon_animation_data = collect_ribbon_animation_data(reader, &ribbon_emitters)?;
3480
3481 let texture_animation_data = collect_texture_animation_data(reader, &texture_animations)?;
3483
3484 let color_animation_data = collect_color_animation_data(reader, &color_animations)?;
3486
3487 let transparency_animation_data =
3489 collect_transparency_animation_data(reader, &transparency_animations)?;
3490
3491 let event_data = collect_event_data(reader, &events)?;
3493
3494 let attachment_animation_data = collect_attachment_animation_data(reader, &attachments)?;
3496
3497 let camera_animation_data = collect_camera_animation_data(reader, &cameras)?;
3499
3500 let light_animation_data = collect_light_animation_data(reader, &lights)?;
3502
3503 let embedded_skins = collect_embedded_skin_data(reader, &header)?;
3505
3506 let raw_data = M2RawData {
3509 bone_animation_data,
3510 embedded_skins,
3511 particle_animation_data,
3512 ribbon_animation_data,
3513 texture_animation_data,
3514 color_animation_data,
3515 transparency_animation_data,
3516 event_data,
3517 attachment_animation_data,
3518 camera_animation_data,
3519 light_animation_data,
3520 transparency_lookup_table: read_array(
3521 reader,
3522 &header.transparency_lookup_table,
3523 |r| Ok(r.read_u16_le()?),
3524 )?,
3525 texture_animation_lookup: read_array(reader, &header.texture_animation_lookup, |r| {
3526 Ok(r.read_u16_le()?)
3527 })?,
3528 bone_lookup_table: read_array(reader, &header.bone_lookup_table, |r| {
3529 Ok(r.read_u16_le()?)
3530 })?,
3531 texture_lookup_table: read_array(reader, &header.texture_lookup_table, |r| {
3532 Ok(r.read_u16_le()?)
3533 })?,
3534 texture_units: read_array(reader, &header.texture_units, |r| Ok(r.read_u16_le()?))?,
3535 camera_lookup_table: read_array(reader, &header.camera_lookup_table, |r| {
3536 Ok(r.read_u16_le()?)
3537 })?,
3538 bounding_triangles: read_raw_bytes(reader, &header.bounding_triangles, 2)?, bounding_vertices: read_raw_bytes(reader, &header.bounding_vertices, 12)?, bounding_normals: read_raw_bytes(reader, &header.bounding_normals, 12)?, attachment_lookup_table: read_array(reader, &header.attachment_lookup_table, |r| {
3544 Ok(r.read_u16_le()?)
3545 })?,
3546 ..Default::default()
3547 };
3548
3549 Ok(Self {
3550 header,
3551 name,
3552 global_sequences,
3553 animations,
3554 animation_lookup,
3555 bones,
3556 key_bone_lookup,
3557 vertices,
3558 textures,
3559 materials,
3560 particle_emitters,
3561 ribbon_emitters,
3562 texture_animations,
3563 color_animations,
3564 transparency_animations,
3565 events,
3566 attachments,
3567 cameras,
3568 lights,
3569 raw_data,
3570 skin_file_ids: None,
3571 animation_file_ids: None,
3572 texture_file_ids: None,
3573 physics_file_id: None,
3574 skeleton_file_id: None,
3575 bone_file_ids: None,
3576 lod_data: None,
3577 extended_particle_data: None,
3578 parent_animation_blacklist: None,
3579 parent_animation_data: None,
3580 waterfall_effect: None,
3581 edge_fade_data: None,
3582 model_alpha_data: None,
3583 lighting_details: None,
3584 recursive_particle_ids: None,
3585 geometry_particle_ids: None,
3586 texture_animation_chunk: None,
3587 particle_geoset_data: None,
3588 dboc_chunk: None,
3589 afra_chunk: None,
3590 dpiv_chunk: None,
3591 parent_sequence_bounds: None,
3592 parent_event_data: None,
3593 collision_mesh_data: None,
3594 physics_file_data: None,
3595 })
3596 }
3597
3598 pub fn load<P: AsRef<Path>>(path: P) -> Result<M2Format> {
3600 let mut file = File::open(path)?;
3601 parse_m2(&mut file)
3602 }
3603
3604 pub fn load_legacy<P: AsRef<Path>>(path: P) -> Result<Self> {
3606 let mut file = File::open(path)?;
3607 Self::parse_legacy(&mut file)
3608 }
3609
3610 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
3612 let mut file = File::create(path)?;
3613 self.write(&mut file)
3614 }
3615
3616 pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
3618 let mut data_section = Vec::new();
3620 let mut header = self.header.clone();
3621
3622 let header_size = self.calculate_header_size();
3624 let mut current_offset = header_size as u32;
3625
3626 if let Some(ref name) = self.name {
3628 let name_bytes = name.as_bytes();
3629 let name_len = name_bytes.len() as u32 + 1; header.name = M2Array::new(name_len, current_offset);
3631
3632 data_section.extend_from_slice(name_bytes);
3633 data_section.push(0); current_offset += name_len;
3635 } else {
3636 header.name = M2Array::new(0, 0);
3637 }
3638
3639 if !self.global_sequences.is_empty() {
3641 header.global_sequences =
3642 M2Array::new(self.global_sequences.len() as u32, current_offset);
3643
3644 for &seq in &self.global_sequences {
3645 data_section.extend_from_slice(&seq.to_le_bytes());
3646 }
3647
3648 current_offset += (self.global_sequences.len() * std::mem::size_of::<u32>()) as u32;
3649 } else {
3650 header.global_sequences = M2Array::new(0, 0);
3651 }
3652
3653 if !self.animations.is_empty() {
3655 header.animations = M2Array::new(self.animations.len() as u32, current_offset);
3656
3657 for anim in &self.animations {
3658 let mut anim_data = Vec::new();
3660 anim.write(&mut anim_data, header.version)?;
3661 data_section.extend_from_slice(&anim_data);
3662 }
3663
3664 let anim_size = if header.version <= 256 { 32 } else { 52 };
3666 current_offset += (self.animations.len() * anim_size) as u32;
3667 } else {
3668 header.animations = M2Array::new(0, 0);
3669 }
3670
3671 if !self.animation_lookup.is_empty() {
3673 header.animation_lookup =
3674 M2Array::new(self.animation_lookup.len() as u32, current_offset);
3675
3676 for &lookup in &self.animation_lookup {
3677 data_section.extend_from_slice(&lookup.to_le_bytes());
3678 }
3679
3680 current_offset += (self.animation_lookup.len() * std::mem::size_of::<u16>()) as u32;
3681 } else {
3682 header.animation_lookup = M2Array::new(0, 0);
3683 }
3684
3685 if !self.bones.is_empty() {
3689 header.bones = M2Array::new(self.bones.len() as u32, current_offset);
3690
3691 let bone_size = if header.version < 260 {
3693 108 } else if header.version < 264 {
3695 112 } else {
3697 88 };
3699
3700 let bones_total_size = self.bones.len() * bone_size;
3702 let anim_data_start = current_offset + bones_total_size as u32;
3703
3704 if !self.raw_data.bone_animation_data.is_empty() {
3706 let mut offset_map: HashMap<u32, u32> = HashMap::new();
3710 let mut anim_data_offset = anim_data_start;
3711
3712 use std::collections::hash_map::Entry;
3713
3714 for anim in &self.raw_data.bone_animation_data {
3715 if !anim.timestamps.is_empty()
3717 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
3718 {
3719 e.insert(anim_data_offset);
3720 anim_data_offset += anim.timestamps.len() as u32;
3721 }
3722
3723 if !anim.values.is_empty()
3725 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
3726 {
3727 e.insert(anim_data_offset);
3728 anim_data_offset += anim.values.len() as u32;
3729 }
3730
3731 if let (Some(ranges), Some(orig_offset)) =
3733 (&anim.ranges, anim.original_ranges_offset)
3734 && let Entry::Vacant(e) = offset_map.entry(orig_offset)
3735 {
3736 e.insert(anim_data_offset);
3737 anim_data_offset += ranges.len() as u32;
3738 }
3739 }
3740
3741 for bone in &self.bones {
3743 let mut relocated_bone = bone.clone();
3744 relocate_bone_track_offsets(&mut relocated_bone, &offset_map);
3745
3746 let mut bone_data = Vec::new();
3747 relocated_bone.write(&mut bone_data, header.version)?;
3748 data_section.extend_from_slice(&bone_data);
3749 }
3750
3751 let mut written_offsets: std::collections::HashSet<u32> =
3753 std::collections::HashSet::new();
3754
3755 for anim in &self.raw_data.bone_animation_data {
3756 if !anim.timestamps.is_empty()
3758 && written_offsets.insert(anim.original_timestamps_offset)
3759 {
3760 data_section.extend_from_slice(&anim.timestamps);
3761 }
3762
3763 if !anim.values.is_empty()
3765 && written_offsets.insert(anim.original_values_offset)
3766 {
3767 data_section.extend_from_slice(&anim.values);
3768 }
3769
3770 if let (Some(ranges), Some(orig_offset)) =
3772 (&anim.ranges, anim.original_ranges_offset)
3773 && written_offsets.insert(orig_offset)
3774 {
3775 data_section.extend_from_slice(ranges);
3776 }
3777 }
3778
3779 current_offset = anim_data_offset;
3780 } else {
3781 for bone in &self.bones {
3783 let mut static_bone = bone.clone();
3784 static_bone.translation = M2TrackVec3::default();
3785 static_bone.rotation = M2TrackQuat::default();
3786 static_bone.scale = M2TrackVec3::default();
3787
3788 let mut bone_data = Vec::new();
3789 static_bone.write(&mut bone_data, header.version)?;
3790 data_section.extend_from_slice(&bone_data);
3791 }
3792
3793 current_offset += bones_total_size as u32;
3794 }
3795 } else {
3796 header.bones = M2Array::new(0, 0);
3797 }
3798
3799 if !self.key_bone_lookup.is_empty() {
3801 header.key_bone_lookup =
3802 M2Array::new(self.key_bone_lookup.len() as u32, current_offset);
3803
3804 for &lookup in &self.key_bone_lookup {
3805 data_section.extend_from_slice(&lookup.to_le_bytes());
3806 }
3807
3808 current_offset += (self.key_bone_lookup.len() * std::mem::size_of::<u16>()) as u32;
3809 } else {
3810 header.key_bone_lookup = M2Array::new(0, 0);
3811 }
3812
3813 if !self.vertices.is_empty() {
3815 header.vertices = M2Array::new(self.vertices.len() as u32, current_offset);
3816
3817 let vertex_size = 48;
3821
3822 for vertex in &self.vertices {
3823 let mut vertex_data = Vec::new();
3824 vertex.write(&mut vertex_data, header.version)?;
3825 data_section.extend_from_slice(&vertex_data);
3826 }
3827
3828 current_offset += (self.vertices.len() * vertex_size) as u32;
3829 } else {
3830 header.vertices = M2Array::new(0, 0);
3831 }
3832
3833 if !self.textures.is_empty() {
3835 header.textures = M2Array::new(self.textures.len() as u32, current_offset);
3836
3837 let mut texture_name_offsets = Vec::new();
3839 let texture_def_size = 16; for texture in &self.textures {
3842 texture_name_offsets
3844 .push(current_offset + (self.textures.len() * texture_def_size) as u32);
3845
3846 let mut texture_def = Vec::new();
3848
3849 texture_def.extend_from_slice(&(texture.texture_type as u32).to_le_bytes());
3851
3852 texture_def.extend_from_slice(&texture.flags.bits().to_le_bytes());
3854
3855 texture_def.extend_from_slice(&0u32.to_le_bytes()); texture_def.extend_from_slice(&0u32.to_le_bytes()); data_section.extend_from_slice(&texture_def);
3860 }
3861
3862 current_offset += (self.textures.len() * texture_def_size) as u32;
3864
3865 for (i, texture) in self.textures.iter().enumerate() {
3867 let filename_offset = texture.filename.array.offset as usize;
3869 let filename_len = texture.filename.array.count as usize;
3870 if filename_offset == 0 || filename_len == 0 {
3872 continue;
3873 }
3874
3875 let base_data_offset = std::mem::size_of::<M2Header>();
3878 let def_offset_in_data = (header.textures.offset as usize - base_data_offset)
3879 + (i * texture_def_size)
3880 + 8;
3881
3882 data_section[def_offset_in_data..def_offset_in_data + 4]
3884 .copy_from_slice(&(filename_len as u32).to_le_bytes());
3885 data_section[def_offset_in_data + 4..def_offset_in_data + 8]
3886 .copy_from_slice(¤t_offset.to_le_bytes());
3887
3888 data_section.extend_from_slice(&texture.filename.string.data);
3890 data_section.push(0); current_offset += filename_len as u32;
3893 }
3894 } else {
3895 header.textures = M2Array::new(0, 0);
3896 }
3897
3898 if !self.materials.is_empty() {
3900 header.render_flags = M2Array::new(self.materials.len() as u32, current_offset);
3901
3902 for material in &self.materials {
3903 let mut material_data = Vec::new();
3904 material.write(&mut material_data, header.version)?;
3905 data_section.extend_from_slice(&material_data);
3906 }
3907
3908 current_offset += (self.materials.len() * 4) as u32;
3910 } else {
3911 header.render_flags = M2Array::new(0, 0);
3912 }
3913
3914 if !self.raw_data.bone_lookup_table.is_empty() {
3916 header.bone_lookup_table =
3917 M2Array::new(self.raw_data.bone_lookup_table.len() as u32, current_offset);
3918
3919 for &lookup in &self.raw_data.bone_lookup_table {
3920 data_section.extend_from_slice(&lookup.to_le_bytes());
3921 }
3922
3923 current_offset +=
3924 (self.raw_data.bone_lookup_table.len() * std::mem::size_of::<u16>()) as u32;
3925 } else {
3926 header.bone_lookup_table = M2Array::new(0, 0);
3927 }
3928
3929 if !self.raw_data.texture_lookup_table.is_empty() {
3931 header.texture_lookup_table = M2Array::new(
3932 self.raw_data.texture_lookup_table.len() as u32,
3933 current_offset,
3934 );
3935
3936 for &lookup in &self.raw_data.texture_lookup_table {
3937 data_section.extend_from_slice(&lookup.to_le_bytes());
3938 }
3939
3940 current_offset +=
3941 (self.raw_data.texture_lookup_table.len() * std::mem::size_of::<u16>()) as u32;
3942 } else {
3943 header.texture_lookup_table = M2Array::new(0, 0);
3944 }
3945
3946 if !self.raw_data.texture_units.is_empty() {
3948 header.texture_units =
3949 M2Array::new(self.raw_data.texture_units.len() as u32, current_offset);
3950
3951 for &unit in &self.raw_data.texture_units {
3952 data_section.extend_from_slice(&unit.to_le_bytes());
3953 }
3954
3955 current_offset +=
3956 (self.raw_data.texture_units.len() * std::mem::size_of::<u16>()) as u32;
3957 } else {
3958 header.texture_units = M2Array::new(0, 0);
3959 }
3960
3961 if !self.raw_data.transparency_lookup_table.is_empty() {
3963 header.transparency_lookup_table = M2Array::new(
3964 self.raw_data.transparency_lookup_table.len() as u32,
3965 current_offset,
3966 );
3967
3968 for &lookup in &self.raw_data.transparency_lookup_table {
3969 data_section.extend_from_slice(&lookup.to_le_bytes());
3970 }
3971
3972 current_offset +=
3973 (self.raw_data.transparency_lookup_table.len() * std::mem::size_of::<u16>()) as u32;
3974 } else {
3975 header.transparency_lookup_table = M2Array::new(0, 0);
3976 }
3977
3978 if !self.raw_data.texture_animation_lookup.is_empty() {
3980 header.texture_animation_lookup = M2Array::new(
3981 self.raw_data.texture_animation_lookup.len() as u32,
3982 current_offset,
3983 );
3984
3985 for &lookup in &self.raw_data.texture_animation_lookup {
3986 data_section.extend_from_slice(&lookup.to_le_bytes());
3987 }
3988
3989 current_offset +=
3990 (self.raw_data.texture_animation_lookup.len() * std::mem::size_of::<u16>()) as u32;
3991 } else {
3992 header.texture_animation_lookup = M2Array::new(0, 0);
3993 }
3994
3995 if !self.raw_data.bounding_triangles.is_empty() {
3997 let count = self.raw_data.bounding_triangles.len() / 2;
3999 header.bounding_triangles = M2Array::new(count as u32, current_offset);
4000 data_section.extend_from_slice(&self.raw_data.bounding_triangles);
4001 current_offset += self.raw_data.bounding_triangles.len() as u32;
4002 } else {
4003 header.bounding_triangles = M2Array::new(0, 0);
4004 }
4005
4006 if !self.raw_data.bounding_vertices.is_empty() {
4008 let count = self.raw_data.bounding_vertices.len() / 12;
4010 header.bounding_vertices = M2Array::new(count as u32, current_offset);
4011 data_section.extend_from_slice(&self.raw_data.bounding_vertices);
4012 current_offset += self.raw_data.bounding_vertices.len() as u32;
4013 } else {
4014 header.bounding_vertices = M2Array::new(0, 0);
4015 }
4016
4017 if !self.raw_data.bounding_normals.is_empty() {
4019 let count = self.raw_data.bounding_normals.len() / 12;
4021 header.bounding_normals = M2Array::new(count as u32, current_offset);
4022 data_section.extend_from_slice(&self.raw_data.bounding_normals);
4023 current_offset += self.raw_data.bounding_normals.len() as u32;
4024 } else {
4025 header.bounding_normals = M2Array::new(0, 0);
4026 }
4027
4028 if !self.raw_data.attachment_lookup_table.is_empty() {
4030 header.attachment_lookup_table = M2Array::new(
4031 self.raw_data.attachment_lookup_table.len() as u32,
4032 current_offset,
4033 );
4034 for &lookup in &self.raw_data.attachment_lookup_table {
4035 data_section.extend_from_slice(&lookup.to_le_bytes());
4036 }
4037 current_offset +=
4038 (self.raw_data.attachment_lookup_table.len() * std::mem::size_of::<u16>()) as u32;
4039 } else {
4040 header.attachment_lookup_table = M2Array::new(0, 0);
4041 }
4042
4043 if !self.raw_data.camera_lookup_table.is_empty() {
4045 header.camera_lookup_table = M2Array::new(
4046 self.raw_data.camera_lookup_table.len() as u32,
4047 current_offset,
4048 );
4049 for &lookup in &self.raw_data.camera_lookup_table {
4050 data_section.extend_from_slice(&lookup.to_le_bytes());
4051 }
4052 current_offset +=
4053 (self.raw_data.camera_lookup_table.len() * std::mem::size_of::<u16>()) as u32;
4054 } else {
4055 header.camera_lookup_table = M2Array::new(0, 0);
4056 }
4057
4058 if header.version <= 263 && !self.raw_data.embedded_skins.is_empty() {
4060 const MODEL_VIEW_SIZE: u32 = 44;
4065 let model_view_count = self.raw_data.embedded_skins.len() as u32;
4066
4067 let model_views_offset = current_offset;
4069 header.views = M2Array::new(model_view_count, model_views_offset);
4070
4071 let mut data_offset = model_views_offset + (model_view_count * MODEL_VIEW_SIZE);
4073 let submesh_size = if header.version < 260 { 32 } else { 48 };
4074
4075 struct SkinOffsets {
4077 indices_offset: u32,
4078 triangles_offset: u32,
4079 properties_offset: u32,
4080 submeshes_offset: u32,
4081 batches_offset: u32,
4082 }
4083
4084 let mut all_offsets = Vec::with_capacity(self.raw_data.embedded_skins.len());
4085
4086 for skin in &self.raw_data.embedded_skins {
4087 let indices_offset = if skin.indices.is_empty() {
4088 0
4089 } else {
4090 let offset = data_offset;
4091 data_offset += skin.indices.len() as u32;
4092 offset
4093 };
4094
4095 let triangles_offset = if skin.triangles.is_empty() {
4096 0
4097 } else {
4098 let offset = data_offset;
4099 data_offset += skin.triangles.len() as u32;
4100 offset
4101 };
4102
4103 let properties_offset = if skin.properties.is_empty() {
4104 0
4105 } else {
4106 let offset = data_offset;
4107 data_offset += skin.properties.len() as u32;
4108 offset
4109 };
4110
4111 let submeshes_offset = if skin.submeshes.is_empty() {
4112 0
4113 } else {
4114 let offset = data_offset;
4115 data_offset += skin.submeshes.len() as u32;
4116 offset
4117 };
4118
4119 let batches_offset = if skin.batches.is_empty() {
4120 0
4121 } else {
4122 let offset = data_offset;
4123 data_offset += skin.batches.len() as u32;
4124 offset
4125 };
4126
4127 all_offsets.push(SkinOffsets {
4128 indices_offset,
4129 triangles_offset,
4130 properties_offset,
4131 submeshes_offset,
4132 batches_offset,
4133 });
4134 }
4135
4136 for (skin, offsets) in self.raw_data.embedded_skins.iter().zip(all_offsets.iter()) {
4138 let n_indices = (skin.indices.len() / 2) as u32;
4140 let n_triangles = (skin.triangles.len() / 2) as u32;
4141 let n_properties = (skin.properties.len() / 4) as u32;
4142 let n_submeshes = if skin.submeshes.is_empty() {
4143 0
4144 } else {
4145 (skin.submeshes.len() / submesh_size) as u32
4146 };
4147 let n_batches = (skin.batches.len() / 96) as u32;
4148
4149 let bone_count_max = if skin.model_view.len() >= 44 {
4151 u32::from_le_bytes([
4152 skin.model_view[40],
4153 skin.model_view[41],
4154 skin.model_view[42],
4155 skin.model_view[43],
4156 ])
4157 } else {
4158 0
4159 };
4160
4161 data_section.extend_from_slice(&n_indices.to_le_bytes());
4163 data_section.extend_from_slice(&offsets.indices_offset.to_le_bytes());
4164 data_section.extend_from_slice(&n_triangles.to_le_bytes());
4165 data_section.extend_from_slice(&offsets.triangles_offset.to_le_bytes());
4166 data_section.extend_from_slice(&n_properties.to_le_bytes());
4167 data_section.extend_from_slice(&offsets.properties_offset.to_le_bytes());
4168 data_section.extend_from_slice(&n_submeshes.to_le_bytes());
4169 data_section.extend_from_slice(&offsets.submeshes_offset.to_le_bytes());
4170 data_section.extend_from_slice(&n_batches.to_le_bytes());
4171 data_section.extend_from_slice(&offsets.batches_offset.to_le_bytes());
4172 data_section.extend_from_slice(&bone_count_max.to_le_bytes());
4173 }
4174
4175 for skin in &self.raw_data.embedded_skins {
4177 if !skin.indices.is_empty() {
4178 data_section.extend_from_slice(&skin.indices);
4179 }
4180 if !skin.triangles.is_empty() {
4181 data_section.extend_from_slice(&skin.triangles);
4182 }
4183 if !skin.properties.is_empty() {
4184 data_section.extend_from_slice(&skin.properties);
4185 }
4186 if !skin.submeshes.is_empty() {
4187 data_section.extend_from_slice(&skin.submeshes);
4188 }
4189 if !skin.batches.is_empty() {
4190 data_section.extend_from_slice(&skin.batches);
4191 }
4192 }
4193
4194 current_offset = data_offset;
4195 }
4196
4197 if !self.particle_emitters.is_empty() {
4200 header.particle_emitters =
4201 M2Array::new(self.particle_emitters.len() as u32, current_offset);
4202
4203 let mut temp_emitter_data = Vec::new();
4205 for emitter in &self.particle_emitters {
4206 let mut emitter_data = Vec::new();
4207 emitter.write(&mut emitter_data, header.version)?;
4208 temp_emitter_data.push(emitter_data);
4209 }
4210 let emitters_total_size: usize = temp_emitter_data.iter().map(|v| v.len()).sum();
4211
4212 let anim_data_start = current_offset + emitters_total_size as u32;
4214
4215 if !self.raw_data.particle_animation_data.is_empty() {
4217 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4219 let mut anim_data_offset = anim_data_start;
4220
4221 use std::collections::hash_map::Entry;
4222
4223 for anim in &self.raw_data.particle_animation_data {
4224 if !anim.interpolation_ranges.is_empty()
4226 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
4227 {
4228 e.insert(anim_data_offset);
4229 anim_data_offset += anim.interpolation_ranges.len() as u32;
4230 }
4231
4232 if !anim.timestamps.is_empty()
4234 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
4235 {
4236 e.insert(anim_data_offset);
4237 anim_data_offset += anim.timestamps.len() as u32;
4238 }
4239
4240 if !anim.values.is_empty()
4242 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
4243 {
4244 e.insert(anim_data_offset);
4245 anim_data_offset += anim.values.len() as u32;
4246 }
4247 }
4248
4249 for emitter in &self.particle_emitters {
4251 let mut relocated_emitter = emitter.clone();
4252 relocate_particle_animation_offsets(&mut relocated_emitter, &offset_map);
4253
4254 let mut emitter_data = Vec::new();
4255 relocated_emitter.write(&mut emitter_data, header.version)?;
4256 data_section.extend_from_slice(&emitter_data);
4257 }
4258
4259 let mut written_offsets: std::collections::HashSet<u32> =
4261 std::collections::HashSet::new();
4262
4263 for anim in &self.raw_data.particle_animation_data {
4264 if !anim.interpolation_ranges.is_empty()
4266 && written_offsets.insert(anim.original_ranges_offset)
4267 {
4268 data_section.extend_from_slice(&anim.interpolation_ranges);
4269 }
4270
4271 if !anim.timestamps.is_empty()
4273 && written_offsets.insert(anim.original_timestamps_offset)
4274 {
4275 data_section.extend_from_slice(&anim.timestamps);
4276 }
4277
4278 if !anim.values.is_empty()
4280 && written_offsets.insert(anim.original_values_offset)
4281 {
4282 data_section.extend_from_slice(&anim.values);
4283 }
4284 }
4285
4286 current_offset = anim_data_offset;
4287 } else {
4288 for emitter in &self.particle_emitters {
4291 let mut static_emitter = emitter.clone();
4292 static_emitter.emission_speed_animation = M2AnimationBlock::default();
4294 static_emitter.emission_rate_animation = M2AnimationBlock::default();
4295 static_emitter.emission_area_animation = M2AnimationBlock::default();
4296 static_emitter.xy_scale_animation = M2AnimationBlock::default();
4297 static_emitter.z_scale_animation = M2AnimationBlock::default();
4298 static_emitter.color_animation = M2AnimationBlock::default();
4299 static_emitter.transparency_animation = M2AnimationBlock::default();
4300 static_emitter.size_animation = M2AnimationBlock::default();
4301 static_emitter.intensity_animation = M2AnimationBlock::default();
4302 static_emitter.z_source_animation = M2AnimationBlock::default();
4303
4304 let mut emitter_data = Vec::new();
4305 static_emitter.write(&mut emitter_data, header.version)?;
4306 data_section.extend_from_slice(&emitter_data);
4307 }
4308
4309 current_offset += emitters_total_size as u32;
4310 }
4311 } else {
4312 header.particle_emitters = M2Array::new(0, 0);
4313 }
4314
4315 if !self.ribbon_emitters.is_empty() {
4318 header.ribbon_emitters =
4319 M2Array::new(self.ribbon_emitters.len() as u32, current_offset);
4320
4321 let mut temp_emitter_data = Vec::new();
4323 for emitter in &self.ribbon_emitters {
4324 let mut emitter_data = Vec::new();
4325 emitter.write(&mut emitter_data, header.version)?;
4326 temp_emitter_data.push(emitter_data);
4327 }
4328 let emitters_total_size: usize = temp_emitter_data.iter().map(|v| v.len()).sum();
4329
4330 let anim_data_start = current_offset + emitters_total_size as u32;
4332
4333 if !self.raw_data.ribbon_animation_data.is_empty() {
4335 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4337 let mut anim_data_offset = anim_data_start;
4338
4339 use std::collections::hash_map::Entry;
4340
4341 for anim in &self.raw_data.ribbon_animation_data {
4342 if !anim.interpolation_ranges.is_empty()
4344 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
4345 {
4346 e.insert(anim_data_offset);
4347 anim_data_offset += anim.interpolation_ranges.len() as u32;
4348 }
4349
4350 if !anim.timestamps.is_empty()
4352 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
4353 {
4354 e.insert(anim_data_offset);
4355 anim_data_offset += anim.timestamps.len() as u32;
4356 }
4357
4358 if !anim.values.is_empty()
4360 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
4361 {
4362 e.insert(anim_data_offset);
4363 anim_data_offset += anim.values.len() as u32;
4364 }
4365 }
4366
4367 for emitter in &self.ribbon_emitters {
4369 let mut relocated_emitter = emitter.clone();
4370 relocate_ribbon_animation_offsets(&mut relocated_emitter, &offset_map);
4371
4372 let mut emitter_data = Vec::new();
4373 relocated_emitter.write(&mut emitter_data, header.version)?;
4374 data_section.extend_from_slice(&emitter_data);
4375 }
4376
4377 let mut written_offsets: std::collections::HashSet<u32> =
4379 std::collections::HashSet::new();
4380
4381 for anim in &self.raw_data.ribbon_animation_data {
4382 if !anim.interpolation_ranges.is_empty()
4384 && written_offsets.insert(anim.original_ranges_offset)
4385 {
4386 data_section.extend_from_slice(&anim.interpolation_ranges);
4387 }
4388
4389 if !anim.timestamps.is_empty()
4391 && written_offsets.insert(anim.original_timestamps_offset)
4392 {
4393 data_section.extend_from_slice(&anim.timestamps);
4394 }
4395
4396 if !anim.values.is_empty()
4398 && written_offsets.insert(anim.original_values_offset)
4399 {
4400 data_section.extend_from_slice(&anim.values);
4401 }
4402 }
4403
4404 current_offset = anim_data_offset;
4405 } else {
4406 for emitter in &self.ribbon_emitters {
4409 let mut static_emitter = emitter.clone();
4410 static_emitter.color_animation = M2AnimationBlock::default();
4412 static_emitter.alpha_animation = M2AnimationBlock::default();
4413 static_emitter.height_above_animation = M2AnimationBlock::default();
4414 static_emitter.height_below_animation = M2AnimationBlock::default();
4415
4416 let mut emitter_data = Vec::new();
4417 static_emitter.write(&mut emitter_data, header.version)?;
4418 data_section.extend_from_slice(&emitter_data);
4419 }
4420
4421 current_offset += emitters_total_size as u32;
4422 }
4423 } else {
4424 header.ribbon_emitters = M2Array::new(0, 0);
4425 }
4426
4427 if !self.texture_animations.is_empty() {
4430 header.texture_animations =
4431 M2Array::new(self.texture_animations.len() as u32, current_offset);
4432
4433 let mut temp_anim_data = Vec::new();
4435 for anim in &self.texture_animations {
4436 let mut anim_data = Vec::new();
4437 anim.write(&mut anim_data)?;
4438 temp_anim_data.push(anim_data);
4439 }
4440 let anims_total_size: usize = temp_anim_data.iter().map(|v| v.len()).sum();
4441
4442 let anim_data_start = current_offset + anims_total_size as u32;
4444
4445 if !self.raw_data.texture_animation_data.is_empty() {
4447 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4449 let mut anim_data_offset = anim_data_start;
4450
4451 use std::collections::hash_map::Entry;
4452
4453 for anim in &self.raw_data.texture_animation_data {
4454 if !anim.interpolation_ranges.is_empty()
4456 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
4457 {
4458 e.insert(anim_data_offset);
4459 anim_data_offset += anim.interpolation_ranges.len() as u32;
4460 }
4461
4462 if !anim.timestamps.is_empty()
4464 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
4465 {
4466 e.insert(anim_data_offset);
4467 anim_data_offset += anim.timestamps.len() as u32;
4468 }
4469
4470 if !anim.values.is_empty()
4472 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
4473 {
4474 e.insert(anim_data_offset);
4475 anim_data_offset += anim.values.len() as u32;
4476 }
4477 }
4478
4479 for anim in &self.texture_animations {
4481 let mut relocated_anim = anim.clone();
4482 relocate_texture_animation_offsets(&mut relocated_anim, &offset_map);
4483
4484 let mut anim_data = Vec::new();
4485 relocated_anim.write(&mut anim_data)?;
4486 data_section.extend_from_slice(&anim_data);
4487 }
4488
4489 let mut written_offsets: std::collections::HashSet<u32> =
4491 std::collections::HashSet::new();
4492
4493 for anim in &self.raw_data.texture_animation_data {
4494 if !anim.interpolation_ranges.is_empty()
4496 && written_offsets.insert(anim.original_ranges_offset)
4497 {
4498 data_section.extend_from_slice(&anim.interpolation_ranges);
4499 }
4500
4501 if !anim.timestamps.is_empty()
4503 && written_offsets.insert(anim.original_timestamps_offset)
4504 {
4505 data_section.extend_from_slice(&anim.timestamps);
4506 }
4507
4508 if !anim.values.is_empty()
4510 && written_offsets.insert(anim.original_values_offset)
4511 {
4512 data_section.extend_from_slice(&anim.values);
4513 }
4514 }
4515
4516 current_offset = anim_data_offset;
4517 } else {
4518 for anim in &self.texture_animations {
4521 let mut static_anim = anim.clone();
4522 static_anim.translation_u = M2AnimationBlock::default();
4524 static_anim.translation_v = M2AnimationBlock::default();
4525 static_anim.rotation = M2AnimationBlock::default();
4526 static_anim.scale_u = M2AnimationBlock::default();
4527 static_anim.scale_v = M2AnimationBlock::default();
4528
4529 let mut anim_data = Vec::new();
4530 static_anim.write(&mut anim_data)?;
4531 data_section.extend_from_slice(&anim_data);
4532 }
4533
4534 current_offset += anims_total_size as u32;
4535 }
4536 } else {
4537 header.texture_animations = M2Array::new(0, 0);
4538 }
4539
4540 if !self.color_animations.is_empty() {
4543 header.color_animations =
4544 M2Array::new(self.color_animations.len() as u32, current_offset);
4545
4546 let mut temp_anim_data = Vec::new();
4548 for anim in &self.color_animations {
4549 let mut anim_data = Vec::new();
4550 anim.write(&mut anim_data)?;
4551 temp_anim_data.push(anim_data);
4552 }
4553 let anims_total_size: usize = temp_anim_data.iter().map(|v| v.len()).sum();
4554
4555 let anim_data_start = current_offset + anims_total_size as u32;
4557
4558 if !self.raw_data.color_animation_data.is_empty() {
4560 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4562 let mut anim_data_offset = anim_data_start;
4563
4564 use std::collections::hash_map::Entry;
4565
4566 for anim in &self.raw_data.color_animation_data {
4567 if !anim.interpolation_ranges.is_empty()
4569 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
4570 {
4571 e.insert(anim_data_offset);
4572 anim_data_offset += anim.interpolation_ranges.len() as u32;
4573 }
4574
4575 if !anim.timestamps.is_empty()
4577 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
4578 {
4579 e.insert(anim_data_offset);
4580 anim_data_offset += anim.timestamps.len() as u32;
4581 }
4582
4583 if !anim.values.is_empty()
4585 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
4586 {
4587 e.insert(anim_data_offset);
4588 anim_data_offset += anim.values.len() as u32;
4589 }
4590 }
4591
4592 for anim in &self.color_animations {
4594 let mut relocated_anim = anim.clone();
4595 relocate_color_animation_offsets(&mut relocated_anim, &offset_map);
4596
4597 let mut anim_data = Vec::new();
4598 relocated_anim.write(&mut anim_data)?;
4599 data_section.extend_from_slice(&anim_data);
4600 }
4601
4602 let mut written_offsets: std::collections::HashSet<u32> =
4604 std::collections::HashSet::new();
4605
4606 for anim in &self.raw_data.color_animation_data {
4607 if !anim.interpolation_ranges.is_empty()
4609 && written_offsets.insert(anim.original_ranges_offset)
4610 {
4611 data_section.extend_from_slice(&anim.interpolation_ranges);
4612 }
4613
4614 if !anim.timestamps.is_empty()
4616 && written_offsets.insert(anim.original_timestamps_offset)
4617 {
4618 data_section.extend_from_slice(&anim.timestamps);
4619 }
4620
4621 if !anim.values.is_empty()
4623 && written_offsets.insert(anim.original_values_offset)
4624 {
4625 data_section.extend_from_slice(&anim.values);
4626 }
4627 }
4628
4629 current_offset = anim_data_offset;
4630 } else {
4631 for anim in &self.color_animations {
4633 let mut static_anim = anim.clone();
4634 static_anim.color = M2AnimationBlock::default();
4636 static_anim.alpha = M2AnimationBlock::default();
4637
4638 let mut anim_data = Vec::new();
4639 static_anim.write(&mut anim_data)?;
4640 data_section.extend_from_slice(&anim_data);
4641 }
4642
4643 current_offset += anims_total_size as u32;
4644 }
4645 } else {
4646 header.color_animations = M2Array::new(0, 0);
4647 }
4648
4649 if !self.transparency_animations.is_empty() {
4652 header.transparency_lookup =
4653 M2Array::new(self.transparency_animations.len() as u32, current_offset);
4654
4655 let mut temp_anim_data = Vec::new();
4657 for anim in &self.transparency_animations {
4658 let mut anim_data = Vec::new();
4659 anim.write(&mut anim_data)?;
4660 temp_anim_data.push(anim_data);
4661 }
4662 let anims_total_size: usize = temp_anim_data.iter().map(|v| v.len()).sum();
4663
4664 let anim_data_start = current_offset + anims_total_size as u32;
4666
4667 if !self.raw_data.transparency_animation_data.is_empty() {
4669 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4671 let mut anim_data_offset = anim_data_start;
4672
4673 use std::collections::hash_map::Entry;
4674
4675 for anim in &self.raw_data.transparency_animation_data {
4676 if !anim.interpolation_ranges.is_empty()
4678 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
4679 {
4680 e.insert(anim_data_offset);
4681 anim_data_offset += anim.interpolation_ranges.len() as u32;
4682 }
4683
4684 if !anim.timestamps.is_empty()
4686 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
4687 {
4688 e.insert(anim_data_offset);
4689 anim_data_offset += anim.timestamps.len() as u32;
4690 }
4691
4692 if !anim.values.is_empty()
4694 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
4695 {
4696 e.insert(anim_data_offset);
4697 anim_data_offset += anim.values.len() as u32;
4698 }
4699 }
4700
4701 for anim in &self.transparency_animations {
4703 let mut relocated_anim = anim.clone();
4704 relocate_transparency_animation_offsets(&mut relocated_anim, &offset_map);
4705
4706 let mut anim_data = Vec::new();
4707 relocated_anim.write(&mut anim_data)?;
4708 data_section.extend_from_slice(&anim_data);
4709 }
4710
4711 let mut written_offsets: std::collections::HashSet<u32> =
4713 std::collections::HashSet::new();
4714
4715 for anim in &self.raw_data.transparency_animation_data {
4716 if !anim.interpolation_ranges.is_empty()
4718 && written_offsets.insert(anim.original_ranges_offset)
4719 {
4720 data_section.extend_from_slice(&anim.interpolation_ranges);
4721 }
4722
4723 if !anim.timestamps.is_empty()
4725 && written_offsets.insert(anim.original_timestamps_offset)
4726 {
4727 data_section.extend_from_slice(&anim.timestamps);
4728 }
4729
4730 if !anim.values.is_empty()
4732 && written_offsets.insert(anim.original_values_offset)
4733 {
4734 data_section.extend_from_slice(&anim.values);
4735 }
4736 }
4737
4738 current_offset = anim_data_offset;
4739 } else {
4740 for anim in &self.transparency_animations {
4742 let mut static_anim = anim.clone();
4743 static_anim.alpha = M2AnimationBlock::default();
4745
4746 let mut anim_data = Vec::new();
4747 static_anim.write(&mut anim_data)?;
4748 data_section.extend_from_slice(&anim_data);
4749 }
4750
4751 current_offset += anims_total_size as u32;
4752 }
4753 } else {
4754 header.transparency_lookup = M2Array::new(0, 0);
4755 }
4756
4757 if !self.events.is_empty() {
4762 let event_offset = self.calculate_header_size() + data_section.len();
4764 header.events = M2Array::new(self.events.len() as u32, event_offset as u32);
4765
4766 let mut temp_event_data = Vec::new();
4768 for event in &self.events {
4769 let mut event_data = Vec::new();
4770 event.write(&mut event_data, header.version)?;
4771 temp_event_data.push(event_data);
4772 }
4773 let events_total_size: usize = temp_event_data.iter().map(|v| v.len()).sum();
4774
4775 let event_data_start = current_offset + events_total_size as u32;
4777
4778 if !self.raw_data.event_data.is_empty() {
4780 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4782 let mut event_data_offset = event_data_start;
4783
4784 use std::collections::hash_map::Entry;
4785
4786 for event_raw in &self.raw_data.event_data {
4787 if !event_raw.timestamps.is_empty()
4789 && let Entry::Vacant(e) =
4790 offset_map.entry(event_raw.original_timestamps_offset)
4791 {
4792 e.insert(event_data_offset);
4793 event_data_offset += event_raw.timestamps.len() as u32;
4794 }
4795 }
4796
4797 for event in &self.events {
4799 let mut relocated_event = event.clone();
4800 relocate_event_offset(&mut relocated_event, &offset_map);
4801
4802 let mut event_data = Vec::new();
4803 relocated_event.write(&mut event_data, header.version)?;
4804 data_section.extend_from_slice(&event_data);
4805 }
4806
4807 let mut written_offsets: std::collections::HashSet<u32> =
4809 std::collections::HashSet::new();
4810
4811 for event_raw in &self.raw_data.event_data {
4812 if !event_raw.ranges.is_empty()
4814 && written_offsets.insert(event_raw.original_ranges_offset)
4815 {
4816 data_section.extend_from_slice(&event_raw.ranges);
4817 }
4818 if !event_raw.timestamps.is_empty()
4820 && written_offsets.insert(event_raw.original_timestamps_offset)
4821 {
4822 data_section.extend_from_slice(&event_raw.timestamps);
4823 }
4824 }
4825
4826 current_offset = event_data_offset;
4827 } else {
4828 for event in &self.events {
4830 let mut static_event = event.clone();
4831 static_event.ranges = M2Array::default();
4833 static_event.times = M2Array::default();
4834
4835 let mut event_data = Vec::new();
4836 static_event.write(&mut event_data, header.version)?;
4837 data_section.extend_from_slice(&event_data);
4838 }
4839
4840 current_offset += events_total_size as u32;
4841 }
4842 } else {
4843 header.events = M2Array::new(0, 0);
4844 }
4845
4846 if !self.attachments.is_empty() {
4851 let attach_offset = self.calculate_header_size() + data_section.len();
4853 header.attachments = M2Array::new(self.attachments.len() as u32, attach_offset as u32);
4854
4855 let mut temp_attach_data = Vec::new();
4857 for attach in &self.attachments {
4858 let mut attach_data = Vec::new();
4859 attach.write(&mut attach_data, header.version)?;
4860 temp_attach_data.push(attach_data);
4861 }
4862 let attachs_total_size: usize = temp_attach_data.iter().map(|v| v.len()).sum();
4863
4864 let attach_data_start = current_offset + attachs_total_size as u32;
4866
4867 if !self.raw_data.attachment_animation_data.is_empty() {
4869 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4871 let mut attach_data_offset = attach_data_start;
4872
4873 use std::collections::hash_map::Entry;
4874
4875 for anim in &self.raw_data.attachment_animation_data {
4876 if !anim.interpolation_ranges.is_empty()
4878 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
4879 {
4880 e.insert(attach_data_offset);
4881 attach_data_offset += anim.interpolation_ranges.len() as u32;
4882 }
4883
4884 if !anim.timestamps.is_empty()
4886 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
4887 {
4888 e.insert(attach_data_offset);
4889 attach_data_offset += anim.timestamps.len() as u32;
4890 }
4891
4892 if !anim.values.is_empty()
4894 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
4895 {
4896 e.insert(attach_data_offset);
4897 attach_data_offset += anim.values.len() as u32;
4898 }
4899 }
4900
4901 for attach in &self.attachments {
4903 let mut relocated_attach = attach.clone();
4904 relocate_attachment_animation_offsets(&mut relocated_attach, &offset_map);
4905
4906 let mut attach_data = Vec::new();
4907 relocated_attach.write(&mut attach_data, header.version)?;
4908 data_section.extend_from_slice(&attach_data);
4909 }
4910
4911 let mut written_offsets: std::collections::HashSet<u32> =
4913 std::collections::HashSet::new();
4914
4915 for anim in &self.raw_data.attachment_animation_data {
4916 if !anim.interpolation_ranges.is_empty()
4918 && written_offsets.insert(anim.original_ranges_offset)
4919 {
4920 data_section.extend_from_slice(&anim.interpolation_ranges);
4921 }
4922
4923 if !anim.timestamps.is_empty()
4925 && written_offsets.insert(anim.original_timestamps_offset)
4926 {
4927 data_section.extend_from_slice(&anim.timestamps);
4928 }
4929
4930 if !anim.values.is_empty()
4932 && written_offsets.insert(anim.original_values_offset)
4933 {
4934 data_section.extend_from_slice(&anim.values);
4935 }
4936 }
4937
4938 current_offset = attach_data_offset;
4939 } else {
4940 for attach in &self.attachments {
4942 let mut static_attach = attach.clone();
4943 static_attach.scale_animation = M2AnimationBlock::default();
4945
4946 let mut attach_data = Vec::new();
4947 static_attach.write(&mut attach_data, header.version)?;
4948 data_section.extend_from_slice(&attach_data);
4949 }
4950
4951 current_offset += attachs_total_size as u32;
4952 }
4953 } else {
4954 header.attachments = M2Array::new(0, 0);
4955 }
4956
4957 if !self.cameras.is_empty() {
4962 let camera_offset = self.calculate_header_size() + data_section.len();
4964 header.cameras = M2Array::new(self.cameras.len() as u32, camera_offset as u32);
4965
4966 let mut temp_camera_data = Vec::new();
4968 for camera in &self.cameras {
4969 let mut camera_data = Vec::new();
4970 camera.write(&mut camera_data, header.version)?;
4971 temp_camera_data.push(camera_data);
4972 }
4973 let cameras_total_size: usize = temp_camera_data.iter().map(|v| v.len()).sum();
4974
4975 let camera_data_start = current_offset + cameras_total_size as u32;
4977
4978 if !self.raw_data.camera_animation_data.is_empty() {
4980 let mut offset_map: HashMap<u32, u32> = HashMap::new();
4982 let mut camera_data_offset = camera_data_start;
4983
4984 use std::collections::hash_map::Entry;
4985
4986 for anim in &self.raw_data.camera_animation_data {
4987 if !anim.interpolation_ranges.is_empty()
4989 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
4990 {
4991 e.insert(camera_data_offset);
4992 camera_data_offset += anim.interpolation_ranges.len() as u32;
4993 }
4994
4995 if !anim.timestamps.is_empty()
4997 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
4998 {
4999 e.insert(camera_data_offset);
5000 camera_data_offset += anim.timestamps.len() as u32;
5001 }
5002
5003 if !anim.values.is_empty()
5005 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
5006 {
5007 e.insert(camera_data_offset);
5008 camera_data_offset += anim.values.len() as u32;
5009 }
5010 }
5011
5012 for camera in &self.cameras {
5014 let mut relocated_camera = camera.clone();
5015 relocate_camera_animation_offsets(&mut relocated_camera, &offset_map);
5016
5017 let mut camera_data = Vec::new();
5018 relocated_camera.write(&mut camera_data, header.version)?;
5019 data_section.extend_from_slice(&camera_data);
5020 }
5021
5022 let mut written_offsets: std::collections::HashSet<u32> =
5024 std::collections::HashSet::new();
5025
5026 for anim in &self.raw_data.camera_animation_data {
5027 if !anim.interpolation_ranges.is_empty()
5029 && written_offsets.insert(anim.original_ranges_offset)
5030 {
5031 data_section.extend_from_slice(&anim.interpolation_ranges);
5032 }
5033
5034 if !anim.timestamps.is_empty()
5036 && written_offsets.insert(anim.original_timestamps_offset)
5037 {
5038 data_section.extend_from_slice(&anim.timestamps);
5039 }
5040
5041 if !anim.values.is_empty()
5043 && written_offsets.insert(anim.original_values_offset)
5044 {
5045 data_section.extend_from_slice(&anim.values);
5046 }
5047 }
5048
5049 current_offset = camera_data_offset;
5050 } else {
5051 for camera in &self.cameras {
5053 let mut static_camera = camera.clone();
5054 static_camera.position_animation = M2AnimationBlock::default();
5056 static_camera.target_position_animation = M2AnimationBlock::default();
5057 static_camera.roll_animation = M2AnimationBlock::default();
5058
5059 let mut camera_data = Vec::new();
5060 static_camera.write(&mut camera_data, header.version)?;
5061 data_section.extend_from_slice(&camera_data);
5062 }
5063
5064 current_offset += cameras_total_size as u32;
5065 }
5066 } else {
5067 header.cameras = M2Array::new(0, 0);
5068 }
5069
5070 if !self.lights.is_empty() {
5075 let light_offset = self.calculate_header_size() + data_section.len();
5077 header.lights = M2Array::new(self.lights.len() as u32, light_offset as u32);
5078
5079 let mut temp_light_data = Vec::new();
5081 for light in &self.lights {
5082 let mut light_data = Vec::new();
5083 light.write(&mut light_data, header.version)?;
5084 temp_light_data.push(light_data);
5085 }
5086 let lights_total_size: usize = temp_light_data.iter().map(|v| v.len()).sum();
5087
5088 let light_data_start = current_offset + lights_total_size as u32;
5090
5091 if !self.raw_data.light_animation_data.is_empty() {
5093 let mut offset_map: HashMap<u32, u32> = HashMap::new();
5095 let mut light_data_offset = light_data_start;
5096
5097 use std::collections::hash_map::Entry;
5098
5099 for anim in &self.raw_data.light_animation_data {
5100 if !anim.interpolation_ranges.is_empty()
5102 && let Entry::Vacant(e) = offset_map.entry(anim.original_ranges_offset)
5103 {
5104 e.insert(light_data_offset);
5105 light_data_offset += anim.interpolation_ranges.len() as u32;
5106 }
5107
5108 if !anim.timestamps.is_empty()
5110 && let Entry::Vacant(e) = offset_map.entry(anim.original_timestamps_offset)
5111 {
5112 e.insert(light_data_offset);
5113 light_data_offset += anim.timestamps.len() as u32;
5114 }
5115
5116 if !anim.values.is_empty()
5118 && let Entry::Vacant(e) = offset_map.entry(anim.original_values_offset)
5119 {
5120 e.insert(light_data_offset);
5121 light_data_offset += anim.values.len() as u32;
5122 }
5123 }
5124
5125 for light in &self.lights {
5127 let mut relocated_light = light.clone();
5128 relocate_light_animation_offsets(&mut relocated_light, &offset_map);
5129
5130 let mut light_data = Vec::new();
5131 relocated_light.write(&mut light_data, header.version)?;
5132 data_section.extend_from_slice(&light_data);
5133 }
5134
5135 let mut written_offsets: std::collections::HashSet<u32> =
5137 std::collections::HashSet::new();
5138
5139 for anim in &self.raw_data.light_animation_data {
5140 if !anim.interpolation_ranges.is_empty()
5142 && written_offsets.insert(anim.original_ranges_offset)
5143 {
5144 data_section.extend_from_slice(&anim.interpolation_ranges);
5145 }
5146
5147 if !anim.timestamps.is_empty()
5149 && written_offsets.insert(anim.original_timestamps_offset)
5150 {
5151 data_section.extend_from_slice(&anim.timestamps);
5152 }
5153
5154 if !anim.values.is_empty()
5156 && written_offsets.insert(anim.original_values_offset)
5157 {
5158 data_section.extend_from_slice(&anim.values);
5159 }
5160 }
5161
5162 current_offset = light_data_offset;
5163 } else {
5164 for light in &self.lights {
5166 let mut static_light = light.clone();
5167 static_light.ambient_color_animation = M2AnimationBlock::default();
5169 static_light.diffuse_color_animation = M2AnimationBlock::default();
5170 static_light.attenuation_start_animation = M2AnimationBlock::default();
5171 static_light.attenuation_end_animation = M2AnimationBlock::default();
5172 static_light.visibility_animation = M2AnimationBlock::default();
5173
5174 let mut light_data = Vec::new();
5175 static_light.write(&mut light_data, header.version)?;
5176 data_section.extend_from_slice(&light_data);
5177 }
5178
5179 current_offset += lights_total_size as u32;
5180 }
5181 } else {
5182 header.lights = M2Array::new(0, 0);
5183 }
5184
5185 header.color_replacements = M2Array::new(0, 0);
5188 if header.version <= 263 {
5194 header.texture_flipbooks = Some(M2Array::new(0, 0));
5195 if self.raw_data.embedded_skins.is_empty() {
5197 header.views = M2Array::new(0, 0);
5198 }
5199 } else {
5200 header.texture_flipbooks = None;
5201 }
5202
5203 if (256..=263).contains(&header.version) {
5206 header.playable_animation_lookup = Some(M2Array::new(0, 0));
5207 } else {
5208 header.playable_animation_lookup = None;
5209 }
5210
5211 header.blend_map_overrides = None;
5213 header.texture_combiner_combos = None;
5214 header.texture_transforms = None;
5215
5216 let _ = current_offset;
5218
5219 header.write(writer)?;
5221 writer.write_all(&data_section)?;
5222
5223 Ok(())
5224 }
5225
5226 pub fn convert(&self, target_version: M2Version) -> Result<Self> {
5228 let source_version = self.header.version().ok_or(M2Error::ConversionError {
5229 from: self.header.version,
5230 to: target_version.to_header_version(),
5231 reason: "Unknown source version".to_string(),
5232 })?;
5233
5234 if source_version == target_version {
5235 return Ok(self.clone());
5236 }
5237
5238 let header = self.header.convert(target_version)?;
5240
5241 let vertices = self
5243 .vertices
5244 .iter()
5245 .map(|v| v.convert(target_version))
5246 .collect();
5247
5248 let textures = self
5250 .textures
5251 .iter()
5252 .map(|t| t.convert(target_version))
5253 .collect();
5254
5255 let bones = self
5257 .bones
5258 .iter()
5259 .map(|b| b.convert(target_version))
5260 .collect();
5261
5262 let materials = self
5264 .materials
5265 .iter()
5266 .map(|m| m.convert(target_version))
5267 .collect();
5268
5269 let mut new_model = self.clone();
5271 new_model.header = header;
5272 new_model.vertices = vertices;
5273 new_model.textures = textures;
5274 new_model.bones = bones;
5275 new_model.materials = materials;
5276
5277 new_model.physics_file_id = self.physics_file_id.clone();
5280 new_model.skeleton_file_id = self.skeleton_file_id.clone();
5281 new_model.bone_file_ids = self.bone_file_ids.clone();
5282 new_model.lod_data = self.lod_data.clone();
5283
5284 Ok(new_model)
5285 }
5286
5287 fn calculate_header_size(&self) -> usize {
5293 let version = self.header.version().unwrap_or(M2Version::Vanilla);
5294
5295 let mut size = 4 + 4; size += 2 * 4; size += 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; let version_num = version.to_header_version();
5308 if (256..=263).contains(&version_num) {
5309 size += 2 * 4; }
5311
5312 size += 2 * 4; size += 2 * 4; size += 2 * 4; if version <= M2Version::TBC {
5319 size += 2 * 4; } else {
5321 size += 4; }
5323
5324 size += 2 * 4; size += 2 * 4; size += 2 * 4; if version <= M2Version::TBC {
5331 size += 2 * 4; }
5333
5334 size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 3 * 4; size += 3 * 4; size += 4; size += 3 * 4; size += 3 * 4; size += 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size += 2 * 4; size
5370 }
5371
5372 pub fn validate(&self) -> Result<()> {
5374 if self.header.version().is_none() {
5376 return Err(M2Error::UnsupportedVersion(self.header.version.to_string()));
5377 }
5378
5379 if self.vertices.is_empty() {
5381 return Err(M2Error::ValidationError(
5382 "Model has no vertices".to_string(),
5383 ));
5384 }
5385
5386 for (i, bone) in self.bones.iter().enumerate() {
5388 if bone.parent_bone >= 0 && bone.parent_bone as usize >= self.bones.len() {
5390 return Err(M2Error::ValidationError(format!(
5391 "Bone {} has invalid parent bone {}",
5392 i, bone.parent_bone
5393 )));
5394 }
5395 }
5396
5397 for (i, texture) in self.textures.iter().enumerate() {
5399 if texture.filename.array.count > 0 && texture.filename.array.offset == 0 {
5401 return Err(M2Error::ValidationError(format!(
5402 "Texture {i} has invalid filename offset"
5403 )));
5404 }
5405 }
5406
5407 for (i, _material) in self.materials.iter().enumerate() {
5409 let _material_index = i; }
5413
5414 Ok(())
5415 }
5416
5417 pub fn has_external_files(&self) -> bool {
5419 self.skin_file_ids.is_some()
5420 || self.animation_file_ids.is_some()
5421 || self.texture_file_ids.is_some()
5422 || self.physics_file_id.is_some()
5423 || self.skeleton_file_id.is_some()
5424 || self.bone_file_ids.is_some()
5425 || self.lod_data.is_some()
5426 || self.has_advanced_features()
5427 }
5428
5429 pub fn skin_file_count(&self) -> usize {
5431 self.skin_file_ids.as_ref().map_or(0, |ids| ids.len())
5432 }
5433
5434 pub fn animation_file_count(&self) -> usize {
5436 self.animation_file_ids.as_ref().map_or(0, |ids| ids.len())
5437 }
5438
5439 pub fn texture_file_count(&self) -> usize {
5441 self.texture_file_ids.as_ref().map_or(0, |ids| ids.len())
5442 }
5443
5444 pub fn resolve_skin_path(&self, index: usize, resolver: &dyn FileResolver) -> Result<String> {
5446 let skin_ids = self.skin_file_ids.as_ref().ok_or_else(|| {
5447 M2Error::ExternalFileError("Model has no external skin files".to_string())
5448 })?;
5449
5450 let id = skin_ids.get(index).ok_or_else(|| {
5451 M2Error::ExternalFileError(format!("Skin index {} out of range", index))
5452 })?;
5453
5454 resolver.resolve_file_data_id(id)
5455 }
5456
5457 pub fn load_skin_file(&self, index: usize, resolver: &dyn FileResolver) -> Result<Vec<u8>> {
5459 let skin_ids = self.skin_file_ids.as_ref().ok_or_else(|| {
5460 M2Error::ExternalFileError("Model has no external skin files".to_string())
5461 })?;
5462
5463 let id = skin_ids.get(index).ok_or_else(|| {
5464 M2Error::ExternalFileError(format!("Skin index {} out of range", index))
5465 })?;
5466
5467 resolver.load_skin_by_id(id)
5468 }
5469
5470 pub fn resolve_animation_path(
5472 &self,
5473 index: usize,
5474 resolver: &dyn FileResolver,
5475 ) -> Result<String> {
5476 let anim_ids = self.animation_file_ids.as_ref().ok_or_else(|| {
5477 M2Error::ExternalFileError("Model has no external animation files".to_string())
5478 })?;
5479
5480 let id = anim_ids.get(index).ok_or_else(|| {
5481 M2Error::ExternalFileError(format!("Animation index {} out of range", index))
5482 })?;
5483
5484 resolver.resolve_file_data_id(id)
5485 }
5486
5487 pub fn load_animation_file(
5489 &self,
5490 index: usize,
5491 resolver: &dyn FileResolver,
5492 ) -> Result<Vec<u8>> {
5493 let anim_ids = self.animation_file_ids.as_ref().ok_or_else(|| {
5494 M2Error::ExternalFileError("Model has no external animation files".to_string())
5495 })?;
5496
5497 let id = anim_ids.get(index).ok_or_else(|| {
5498 M2Error::ExternalFileError(format!("Animation index {} out of range", index))
5499 })?;
5500
5501 resolver.load_animation_by_id(id)
5502 }
5503
5504 pub fn resolve_texture_path(
5507 &self,
5508 index: usize,
5509 resolver: &dyn FileResolver,
5510 ) -> Result<String> {
5511 if let Some(texture_ids) = &self.texture_file_ids
5513 && let Some(id) = texture_ids.get(index)
5514 {
5515 return resolver.resolve_file_data_id(id);
5516 }
5517
5518 if let Some(texture) = self.textures.get(index)
5520 && !texture.filename.string.data.is_empty()
5521 {
5522 let filename = String::from_utf8_lossy(&texture.filename.string.data).to_string();
5523 return Ok(filename.trim_end_matches('\0').to_string());
5524 }
5525
5526 Err(M2Error::ExternalFileError(format!(
5527 "Texture index {} not found",
5528 index
5529 )))
5530 }
5531
5532 pub fn load_texture_file(&self, index: usize, resolver: &dyn FileResolver) -> Result<Vec<u8>> {
5535 if let Some(texture_ids) = &self.texture_file_ids
5537 && let Some(id) = texture_ids.get(index)
5538 {
5539 return resolver.load_texture_by_id(id);
5540 }
5541
5542 if let Some(texture) = self.textures.get(index)
5545 && !texture.filename.string.data.is_empty()
5546 {
5547 let filename = String::from_utf8_lossy(&texture.filename.string.data).to_string();
5548 let clean_filename = filename.trim_end_matches('\0').to_string();
5549 return Err(M2Error::ExternalFileError(format!(
5550 "Cannot load pre-Legion texture '{}' without FileDataID",
5551 clean_filename
5552 )));
5553 }
5554
5555 Err(M2Error::ExternalFileError(format!(
5556 "Texture index {} not found",
5557 index
5558 )))
5559 }
5560
5561 pub fn get_skin_file_ids(&self) -> Option<&[u32]> {
5563 self.skin_file_ids.as_ref().map(|ids| ids.ids.as_slice())
5564 }
5565
5566 pub fn get_animation_file_ids(&self) -> Option<&[u32]> {
5568 self.animation_file_ids
5569 .as_ref()
5570 .map(|ids| ids.ids.as_slice())
5571 }
5572
5573 pub fn get_texture_file_ids(&self) -> Option<&[u32]> {
5575 self.texture_file_ids.as_ref().map(|ids| ids.ids.as_slice())
5576 }
5577
5578 pub fn get_physics_file_id(&self) -> Option<u32> {
5580 self.physics_file_id.as_ref().map(|id| id.id)
5581 }
5582
5583 pub fn get_skeleton_file_id(&self) -> Option<u32> {
5585 self.skeleton_file_id.as_ref().map(|id| id.id)
5586 }
5587
5588 pub fn get_bone_file_ids(&self) -> Option<&[u32]> {
5590 self.bone_file_ids.as_ref().map(|ids| ids.ids.as_slice())
5591 }
5592
5593 pub fn get_lod_data(&self) -> Option<&LodData> {
5595 self.lod_data.as_ref()
5596 }
5597
5598 pub fn load_physics(&self, resolver: &dyn FileResolver) -> Result<Option<PhysicsData>> {
5600 match &self.physics_file_id {
5601 Some(pfid) => {
5602 let data = resolver.load_physics_by_id(&pfid.id)?;
5603 Ok(Some(PhysicsData::parse(&data)?))
5604 }
5605 None => Ok(None),
5606 }
5607 }
5608
5609 pub fn load_skeleton(&self, resolver: &dyn FileResolver) -> Result<Option<SkeletonData>> {
5611 match &self.skeleton_file_id {
5612 Some(skid) => {
5613 let data = resolver.load_skeleton_by_id(&skid.id)?;
5614 Ok(Some(SkeletonData::parse(&data)?))
5615 }
5616 None => Ok(None),
5617 }
5618 }
5619
5620 pub fn load_bone_data(
5622 &self,
5623 index: usize,
5624 resolver: &dyn FileResolver,
5625 ) -> Result<Option<BoneData>> {
5626 match &self.bone_file_ids {
5627 Some(bfid) => {
5628 if let Some(id) = bfid.get(index) {
5629 let data = resolver.load_bone_by_id(&id)?;
5630 Ok(Some(BoneData::parse(&data)?))
5631 } else {
5632 Err(M2Error::ExternalFileError(format!(
5633 "Bone index {} out of range",
5634 index
5635 )))
5636 }
5637 }
5638 None => Ok(None),
5639 }
5640 }
5641
5642 pub fn bone_file_count(&self) -> usize {
5644 self.bone_file_ids.as_ref().map_or(0, |ids| ids.len())
5645 }
5646
5647 pub fn select_lod(&self, distance: f32) -> Option<&crate::chunks::file_references::LodLevel> {
5649 self.lod_data.as_ref()?.select_lod(distance)
5650 }
5651
5652 pub fn has_lod_data(&self) -> bool {
5654 self.lod_data.is_some()
5655 }
5656
5657 pub fn is_animation_blacklisted(&self, sequence_id: u16) -> bool {
5659 self.parent_animation_blacklist
5660 .as_ref()
5661 .is_some_and(|pabc| pabc.blacklisted_sequences.contains(&sequence_id))
5662 }
5663
5664 pub fn get_extended_particle_data(&self) -> Option<&ExtendedParticleData> {
5666 self.extended_particle_data.as_ref()
5667 }
5668
5669 pub fn get_parent_animation_blacklist(&self) -> Option<&ParentAnimationBlacklist> {
5671 self.parent_animation_blacklist.as_ref()
5672 }
5673
5674 pub fn get_parent_animation_data(&self) -> Option<&ParentAnimationData> {
5676 self.parent_animation_data.as_ref()
5677 }
5678
5679 pub fn get_waterfall_effect(&self) -> Option<&WaterfallEffect> {
5681 self.waterfall_effect.as_ref()
5682 }
5683
5684 pub fn get_edge_fade_data(&self) -> Option<&EdgeFadeData> {
5686 self.edge_fade_data.as_ref()
5687 }
5688
5689 pub fn get_model_alpha_data(&self) -> Option<&ModelAlphaData> {
5691 self.model_alpha_data.as_ref()
5692 }
5693
5694 pub fn get_lighting_details(&self) -> Option<&LightingDetails> {
5696 self.lighting_details.as_ref()
5697 }
5698
5699 pub fn get_recursive_particle_ids(&self) -> Option<&[u32]> {
5701 self.recursive_particle_ids
5702 .as_ref()
5703 .map(|ids| ids.model_ids.as_slice())
5704 }
5705
5706 pub fn get_geometry_particle_ids(&self) -> Option<&[u32]> {
5708 self.geometry_particle_ids
5709 .as_ref()
5710 .map(|ids| ids.model_ids.as_slice())
5711 }
5712
5713 pub fn load_particle_models(
5716 &self,
5717 file_resolver: &dyn crate::file_resolver::FileResolver,
5718 ) -> crate::error::Result<Vec<M2Model>> {
5719 let mut models = Vec::new();
5720 let mut loaded_ids = std::collections::HashSet::new();
5721
5722 if let Some(rpid) = &self.recursive_particle_ids {
5724 for &id in &rpid.model_ids {
5725 if !loaded_ids.contains(&id) {
5726 loaded_ids.insert(id);
5727
5728 match file_resolver.load_animation_by_id(id) {
5729 Ok(data) => {
5730 let mut cursor = std::io::Cursor::new(data);
5731 match parse_m2(&mut cursor) {
5732 Ok(format) => models.push(format.model().clone()),
5733 Err(e) => {
5734 eprintln!(
5736 "Warning: Failed to load recursive particle model {}: {:?}",
5737 id, e
5738 );
5739 }
5740 }
5741 }
5742 Err(e) => {
5743 eprintln!(
5744 "Warning: Failed to load recursive particle model data {}: {:?}",
5745 id, e
5746 );
5747 }
5748 }
5749 }
5750 }
5751 }
5752
5753 if let Some(gpid) = &self.geometry_particle_ids {
5755 for &id in &gpid.model_ids {
5756 if !loaded_ids.contains(&id) {
5757 loaded_ids.insert(id);
5758
5759 match file_resolver.load_animation_by_id(id) {
5760 Ok(data) => {
5761 let mut cursor = std::io::Cursor::new(data);
5762 match parse_m2(&mut cursor) {
5763 Ok(format) => models.push(format.model().clone()),
5764 Err(e) => {
5765 eprintln!(
5766 "Warning: Failed to load geometry particle model {}: {:?}",
5767 id, e
5768 );
5769 }
5770 }
5771 }
5772 Err(e) => {
5773 eprintln!(
5774 "Warning: Failed to load geometry particle model data {}: {:?}",
5775 id, e
5776 );
5777 }
5778 }
5779 }
5780 }
5781 }
5782
5783 Ok(models)
5784 }
5785
5786 pub fn get_parent_sequence_bounds(&self) -> Option<&ParentSequenceBounds> {
5789 self.parent_sequence_bounds.as_ref()
5790 }
5791
5792 pub fn get_parent_event_data(&self) -> Option<&ParentEventData> {
5794 self.parent_event_data.as_ref()
5795 }
5796
5797 pub fn get_collision_mesh_data(&self) -> Option<&CollisionMeshData> {
5799 self.collision_mesh_data.as_ref()
5800 }
5801
5802 pub fn get_physics_file_data(&self) -> Option<&PhysicsFileDataChunk> {
5804 self.physics_file_data.as_ref()
5805 }
5806
5807 pub fn has_advanced_features(&self) -> bool {
5809 self.extended_particle_data.is_some()
5810 || self.parent_animation_blacklist.is_some()
5811 || self.parent_animation_data.is_some()
5812 || self.parent_sequence_bounds.is_some()
5813 || self.parent_event_data.is_some()
5814 || self.waterfall_effect.is_some()
5815 || self.edge_fade_data.is_some()
5816 || self.model_alpha_data.is_some()
5817 || self.lighting_details.is_some()
5818 || self.recursive_particle_ids.is_some()
5819 || self.geometry_particle_ids.is_some()
5820 || self.collision_mesh_data.is_some()
5821 || self.physics_file_data.is_some()
5822 }
5823}
5824
5825#[cfg(test)]
5826mod tests {
5827 use super::*;
5828 use crate::chunks::{AnimationFileIds, SkinFileIds, TextureFileIds};
5829 use crate::header::{M2_MAGIC_CHUNKED, M2_MAGIC_LEGACY};
5830 use std::io::Cursor;
5831
5832 #[test]
5833 fn test_m2_format_detection_legacy() {
5834 let mut data = Vec::new();
5836 data.extend_from_slice(&M2_MAGIC_LEGACY); data.extend_from_slice(&256u32.to_le_bytes()); for _ in 0..100 {
5840 data.extend_from_slice(&0u32.to_le_bytes());
5841 }
5842
5843 let mut cursor = Cursor::new(data);
5844 let result = parse_m2(&mut cursor);
5845
5846 assert!(result.is_ok());
5847 let format = result.unwrap();
5848 assert!(format.is_legacy());
5849 assert!(!format.is_chunked());
5850 }
5851
5852 #[test]
5853 fn test_m2_format_detection_chunked() {
5854 let mut data = Vec::new();
5856 data.extend_from_slice(&M2_MAGIC_CHUNKED); data.extend_from_slice(&8u32.to_le_bytes()); data.extend_from_slice(b"MD21"); data.extend_from_slice(&400u32.to_le_bytes()); data.extend_from_slice(&M2_MAGIC_LEGACY); data.extend_from_slice(&276u32.to_le_bytes()); for _ in 0..100 {
5868 data.extend_from_slice(&0u32.to_le_bytes());
5869 }
5870
5871 let mut cursor = Cursor::new(data);
5872 let result = parse_m2(&mut cursor);
5873
5874 match result {
5877 Ok(format) => {
5878 assert!(format.is_chunked());
5879 assert!(!format.is_legacy());
5880 }
5881 Err(M2Error::ParseError(msg)) => {
5882 assert!(
5884 msg.contains("TODO") || msg.contains("not yet") || msg.contains("incomplete")
5885 );
5886 }
5887 Err(other) => panic!("Unexpected error: {:?}", other),
5888 }
5889 }
5890
5891 #[test]
5892 fn test_invalid_magic_detection() {
5893 let data = b"FAIL\x00\x00\x00\x00"; let mut cursor = Cursor::new(data);
5895 let result = parse_m2(&mut cursor);
5896
5897 assert!(result.is_err());
5898 match result.unwrap_err() {
5899 M2Error::InvalidMagicBytes(magic) => {
5900 assert_eq!(&magic, b"FAIL");
5901 }
5902 other => panic!("Expected InvalidMagicBytes error, got: {:?}", other),
5903 }
5904 }
5905
5906 #[test]
5907 fn test_m2_format_model_access() {
5908 use crate::version::M2Version;
5910
5911 let mut test_model = M2Model {
5913 header: M2Header::new(M2Version::Vanilla),
5914 name: Some("test".to_string()),
5915 global_sequences: Vec::new(),
5916 animations: Vec::new(),
5917 animation_lookup: Vec::new(),
5918 bones: Vec::new(),
5919 key_bone_lookup: Vec::new(),
5920 vertices: Vec::new(),
5921 textures: Vec::new(),
5922 materials: Vec::new(),
5923 particle_emitters: Vec::new(),
5924 ribbon_emitters: Vec::new(),
5925 texture_animations: Vec::new(),
5926 color_animations: Vec::new(),
5927 transparency_animations: Vec::new(),
5928 events: Vec::new(),
5929 attachments: Vec::new(),
5930 cameras: Vec::new(),
5931 lights: Vec::new(),
5932 raw_data: M2RawData::default(),
5933 skin_file_ids: None,
5934 animation_file_ids: None,
5935 texture_file_ids: None,
5936 physics_file_id: None,
5937 skeleton_file_id: None,
5938 bone_file_ids: None,
5939 lod_data: None,
5940 extended_particle_data: None,
5941 parent_animation_blacklist: None,
5942 parent_animation_data: None,
5943 waterfall_effect: None,
5944 edge_fade_data: None,
5945 model_alpha_data: None,
5946 lighting_details: None,
5947 recursive_particle_ids: None,
5948 geometry_particle_ids: None,
5949 texture_animation_chunk: None,
5950 particle_geoset_data: None,
5951 dboc_chunk: None,
5952 afra_chunk: None,
5953 dpiv_chunk: None,
5954 parent_sequence_bounds: None,
5955 parent_event_data: None,
5956 collision_mesh_data: None,
5957 physics_file_data: None,
5958 };
5959
5960 let legacy_format = M2Format::Legacy(test_model.clone());
5962 assert_eq!(legacy_format.model().name.as_ref().unwrap(), "test");
5963 assert!(legacy_format.is_legacy());
5964
5965 test_model.skin_file_ids = Some(SkinFileIds { ids: vec![1, 2, 3] });
5967 let chunked_format = M2Format::Chunked(test_model.clone());
5968 assert_eq!(chunked_format.model().name.as_ref().unwrap(), "test");
5969 assert!(chunked_format.is_chunked());
5970 assert_eq!(
5971 chunked_format.model().skin_file_ids.as_ref().unwrap().len(),
5972 3
5973 );
5974 }
5975
5976 #[test]
5977 fn test_file_reference_methods() {
5978 use crate::file_resolver::ListfileResolver;
5979 use crate::version::M2Version;
5980
5981 let mut model = M2Model {
5983 header: M2Header::new(M2Version::Legion),
5984 name: Some("test_model".to_string()),
5985 global_sequences: Vec::new(),
5986 animations: Vec::new(),
5987 animation_lookup: Vec::new(),
5988 bones: Vec::new(),
5989 key_bone_lookup: Vec::new(),
5990 vertices: Vec::new(),
5991 textures: Vec::new(),
5992 materials: Vec::new(),
5993 particle_emitters: Vec::new(),
5994 ribbon_emitters: Vec::new(),
5995 texture_animations: Vec::new(),
5996 color_animations: Vec::new(),
5997 transparency_animations: Vec::new(),
5998 events: Vec::new(),
5999 attachments: Vec::new(),
6000 cameras: Vec::new(),
6001 lights: Vec::new(),
6002 raw_data: M2RawData::default(),
6003 skin_file_ids: Some(SkinFileIds {
6004 ids: vec![123456, 789012],
6005 }),
6006 animation_file_ids: Some(AnimationFileIds {
6007 ids: vec![111111, 222222],
6008 }),
6009 texture_file_ids: Some(TextureFileIds {
6010 ids: vec![333333, 444444],
6011 }),
6012 physics_file_id: None,
6013 skeleton_file_id: None,
6014 bone_file_ids: None,
6015 lod_data: None,
6016 extended_particle_data: None,
6017 parent_animation_blacklist: None,
6018 parent_animation_data: None,
6019 waterfall_effect: None,
6020 edge_fade_data: None,
6021 model_alpha_data: None,
6022 lighting_details: None,
6023 recursive_particle_ids: None,
6024 geometry_particle_ids: None,
6025 texture_animation_chunk: None,
6026 particle_geoset_data: None,
6027 dboc_chunk: None,
6028 afra_chunk: None,
6029 dpiv_chunk: None,
6030 parent_sequence_bounds: None,
6031 parent_event_data: None,
6032 collision_mesh_data: None,
6033 physics_file_data: None,
6034 };
6035
6036 assert!(model.has_external_files());
6038 assert_eq!(model.skin_file_count(), 2);
6039 assert_eq!(model.animation_file_count(), 2);
6040 assert_eq!(model.texture_file_count(), 2);
6041
6042 assert_eq!(
6044 model.get_skin_file_ids(),
6045 Some([123456u32, 789012u32].as_slice())
6046 );
6047 assert_eq!(
6048 model.get_animation_file_ids(),
6049 Some([111111u32, 222222u32].as_slice())
6050 );
6051 assert_eq!(
6052 model.get_texture_file_ids(),
6053 Some([333333u32, 444444u32].as_slice())
6054 );
6055
6056 let mut resolver = ListfileResolver::new();
6058 resolver.add_mapping(123456, "character/human/male/humanmale00.skin");
6059 resolver.add_mapping(789012, "character/human/male/humanmale01.skin");
6060 resolver.add_mapping(111111, "character/human/male/humanmale_walk.anim");
6061 resolver.add_mapping(222222, "character/human/male/humanmale_run.anim");
6062 resolver.add_mapping(333333, "character/textures/skin_human_male.blp");
6063 resolver.add_mapping(444444, "character/textures/hair_human_male.blp");
6064
6065 assert_eq!(
6067 model.resolve_skin_path(0, &resolver).unwrap(),
6068 "character/human/male/humanmale00.skin"
6069 );
6070 assert_eq!(
6071 model.resolve_skin_path(1, &resolver).unwrap(),
6072 "character/human/male/humanmale01.skin"
6073 );
6074 assert!(model.resolve_skin_path(2, &resolver).is_err()); assert_eq!(
6077 model.resolve_animation_path(0, &resolver).unwrap(),
6078 "character/human/male/humanmale_walk.anim"
6079 );
6080 assert_eq!(
6081 model.resolve_animation_path(1, &resolver).unwrap(),
6082 "character/human/male/humanmale_run.anim"
6083 );
6084 assert!(model.resolve_animation_path(2, &resolver).is_err()); assert_eq!(
6087 model.resolve_texture_path(0, &resolver).unwrap(),
6088 "character/textures/skin_human_male.blp"
6089 );
6090 assert_eq!(
6091 model.resolve_texture_path(1, &resolver).unwrap(),
6092 "character/textures/hair_human_male.blp"
6093 );
6094 assert!(model.resolve_texture_path(2, &resolver).is_err()); assert!(model.load_skin_file(0, &resolver).is_err());
6098 assert!(model.load_animation_file(0, &resolver).is_err());
6099 assert!(model.load_texture_file(0, &resolver).is_err());
6100
6101 model.skin_file_ids = None;
6103 model.animation_file_ids = None;
6104 model.texture_file_ids = None;
6105
6106 assert!(!model.has_external_files());
6108
6109 model.extended_particle_data = None;
6111 model.parent_animation_blacklist = None;
6112 model.parent_animation_data = None;
6113 model.waterfall_effect = None;
6114 model.edge_fade_data = None;
6115 model.model_alpha_data = None;
6116 model.lighting_details = None;
6117 model.recursive_particle_ids = None;
6118 model.geometry_particle_ids = None;
6119
6120 assert!(!model.has_external_files());
6121 assert_eq!(model.skin_file_count(), 0);
6122 assert_eq!(model.animation_file_count(), 0);
6123 assert_eq!(model.texture_file_count(), 0);
6124
6125 assert!(model.resolve_skin_path(0, &resolver).is_err());
6126 assert!(model.resolve_animation_path(0, &resolver).is_err());
6127 assert!(model.resolve_texture_path(0, &resolver).is_err());
6128 }
6129
6130 #[test]
6131 fn test_legacy_model_texture_handling() {
6132 use crate::chunks::texture::{M2Texture, M2TextureFlags, M2TextureType};
6133 use crate::common::{FixedString, M2Array, M2ArrayString};
6134 use crate::file_resolver::ListfileResolver;
6135 use crate::version::M2Version;
6136
6137 let texture_filename = "character/textures/skin_human_male.blp";
6139 let mut filename_data = texture_filename.as_bytes().to_vec();
6140 filename_data.push(0); let texture = M2Texture {
6143 texture_type: M2TextureType::Body,
6144 flags: M2TextureFlags::empty(),
6145 filename: M2ArrayString {
6146 array: M2Array::new(filename_data.len() as u32, 0),
6147 string: FixedString {
6148 data: filename_data,
6149 },
6150 },
6151 };
6152
6153 let model = M2Model {
6154 header: M2Header::new(M2Version::Vanilla),
6155 name: Some("legacy_model".to_string()),
6156 global_sequences: Vec::new(),
6157 animations: Vec::new(),
6158 animation_lookup: Vec::new(),
6159 bones: Vec::new(),
6160 key_bone_lookup: Vec::new(),
6161 vertices: Vec::new(),
6162 textures: vec![texture],
6163 materials: Vec::new(),
6164 particle_emitters: Vec::new(),
6165 ribbon_emitters: Vec::new(),
6166 texture_animations: Vec::new(),
6167 color_animations: Vec::new(),
6168 transparency_animations: Vec::new(),
6169 events: Vec::new(),
6170 attachments: Vec::new(),
6171 cameras: Vec::new(),
6172 lights: Vec::new(),
6173 raw_data: M2RawData::default(),
6174 skin_file_ids: None,
6175 animation_file_ids: None,
6176 texture_file_ids: None,
6177 physics_file_id: None,
6178 skeleton_file_id: None,
6179 bone_file_ids: None,
6180 lod_data: None,
6181 extended_particle_data: None,
6182 parent_animation_blacklist: None,
6183 parent_animation_data: None,
6184 waterfall_effect: None,
6185 edge_fade_data: None,
6186 model_alpha_data: None,
6187 lighting_details: None,
6188 recursive_particle_ids: None,
6189 geometry_particle_ids: None,
6190 texture_animation_chunk: None,
6191 particle_geoset_data: None,
6192 dboc_chunk: None,
6193 afra_chunk: None,
6194 dpiv_chunk: None,
6195 parent_sequence_bounds: None,
6196 parent_event_data: None,
6197 collision_mesh_data: None,
6198 physics_file_data: None,
6199 };
6200
6201 let resolver = ListfileResolver::new();
6202
6203 assert_eq!(
6205 model.resolve_texture_path(0, &resolver).unwrap(),
6206 texture_filename
6207 );
6208
6209 match model.load_texture_file(0, &resolver) {
6211 Err(M2Error::ExternalFileError(msg)) => {
6212 assert!(msg.contains("Cannot load pre-Legion texture"));
6213 assert!(msg.contains(texture_filename));
6214 }
6215 _ => panic!("Expected external file error for legacy texture loading"),
6216 }
6217 }
6218
6219 #[test]
6220 fn test_advanced_features() {
6221 use crate::chunks::rendering_enhancements::*;
6222 use crate::file_resolver::ListfileResolver;
6223 use crate::version::M2Version;
6224
6225 let mut model = M2Model {
6227 header: M2Header::new(M2Version::Legion),
6228 name: Some("advanced_model".to_string()),
6229 global_sequences: Vec::new(),
6230 animations: Vec::new(),
6231 animation_lookup: Vec::new(),
6232 bones: Vec::new(),
6233 key_bone_lookup: Vec::new(),
6234 vertices: Vec::new(),
6235 textures: Vec::new(),
6236 materials: Vec::new(),
6237 particle_emitters: Vec::new(),
6238 ribbon_emitters: Vec::new(),
6239 texture_animations: Vec::new(),
6240 color_animations: Vec::new(),
6241 transparency_animations: Vec::new(),
6242 events: Vec::new(),
6243 attachments: Vec::new(),
6244 cameras: Vec::new(),
6245 lights: Vec::new(),
6246 raw_data: M2RawData::default(),
6247 skin_file_ids: None,
6248 animation_file_ids: None,
6249 texture_file_ids: None,
6250 physics_file_id: None,
6251 skeleton_file_id: None,
6252 bone_file_ids: None,
6253 lod_data: None,
6254 extended_particle_data: Some(ExtendedParticleData {
6255 version: 1,
6256 enhanced_emitters: Vec::new(),
6257 particle_systems: Vec::new(),
6258 }),
6259 parent_animation_blacklist: Some(ParentAnimationBlacklist {
6260 blacklisted_sequences: vec![1, 5, 10],
6261 }),
6262 parent_animation_data: Some(ParentAnimationData {
6263 texture_weights: Vec::new(),
6264 blending_modes: Vec::new(),
6265 }),
6266 waterfall_effect: Some(WaterfallEffect {
6267 version: 1,
6268 parameters: WaterfallParameters {
6269 flow_velocity: 1.0,
6270 turbulence: 0.5,
6271 foam_intensity: 0.75,
6272 additional_params: Vec::new(),
6273 },
6274 }),
6275 edge_fade_data: Some(EdgeFadeData {
6276 fade_distances: vec![10.0, 20.0],
6277 fade_factors: vec![0.5, 0.8],
6278 }),
6279 model_alpha_data: Some(ModelAlphaData {
6280 alpha_test_threshold: 0.5,
6281 blend_mode: AlphaBlendMode::Normal,
6282 }),
6283 lighting_details: Some(LightingDetails {
6284 ambient_factor: 0.2,
6285 diffuse_factor: 0.8,
6286 specular_factor: 0.3,
6287 }),
6288 recursive_particle_ids: Some(RecursiveParticleIds {
6289 model_ids: vec![123456, 789012],
6290 }),
6291 geometry_particle_ids: Some(GeometryParticleIds {
6292 model_ids: vec![345678, 901234],
6293 }),
6294 texture_animation_chunk: None,
6295 particle_geoset_data: None,
6296 dboc_chunk: None,
6297 afra_chunk: None,
6298 dpiv_chunk: None,
6299 parent_sequence_bounds: None,
6300 parent_event_data: None,
6301 collision_mesh_data: None,
6302 physics_file_data: None,
6303 };
6304
6305 assert!(model.has_advanced_features());
6307 assert!(model.has_external_files());
6308
6309 assert!(model.is_animation_blacklisted(1));
6311 assert!(model.is_animation_blacklisted(5));
6312 assert!(model.is_animation_blacklisted(10));
6313 assert!(!model.is_animation_blacklisted(2));
6314
6315 assert!(model.get_extended_particle_data().is_some());
6317 assert!(model.get_parent_animation_blacklist().is_some());
6318 assert!(model.get_parent_animation_data().is_some());
6319 assert!(model.get_waterfall_effect().is_some());
6320 assert!(model.get_edge_fade_data().is_some());
6321 assert!(model.get_model_alpha_data().is_some());
6322 assert!(model.get_lighting_details().is_some());
6323
6324 assert_eq!(
6325 model.get_recursive_particle_ids(),
6326 Some([123456u32, 789012u32].as_slice())
6327 );
6328 assert_eq!(
6329 model.get_geometry_particle_ids(),
6330 Some([345678u32, 901234u32].as_slice())
6331 );
6332
6333 let waterfall = model.get_waterfall_effect().unwrap();
6335 assert_eq!(waterfall.version, 1);
6336 assert_eq!(waterfall.parameters.flow_velocity, 1.0);
6337
6338 let edge_fade = model.get_edge_fade_data().unwrap();
6340 assert_eq!(edge_fade.fade_distances, vec![10.0, 20.0]);
6341 assert_eq!(edge_fade.fade_factors, vec![0.5, 0.8]);
6342
6343 let alpha_data = model.get_model_alpha_data().unwrap();
6345 assert_eq!(alpha_data.alpha_test_threshold, 0.5);
6346 assert_eq!(alpha_data.blend_mode, AlphaBlendMode::Normal);
6347
6348 let lighting = model.get_lighting_details().unwrap();
6350 assert_eq!(lighting.ambient_factor, 0.2);
6351 assert_eq!(lighting.diffuse_factor, 0.8);
6352 assert_eq!(lighting.specular_factor, 0.3);
6353
6354 let resolver = ListfileResolver::new();
6356 let result = model.load_particle_models(&resolver);
6357 assert!(result.is_ok()); model.extended_particle_data = None;
6361 model.parent_animation_blacklist = None;
6362 model.parent_animation_data = None;
6363 model.waterfall_effect = None;
6364 model.edge_fade_data = None;
6365 model.model_alpha_data = None;
6366 model.lighting_details = None;
6367 model.recursive_particle_ids = None;
6368 model.geometry_particle_ids = None;
6369
6370 assert!(!model.has_advanced_features());
6371 assert!(!model.has_external_files());
6372 assert!(!model.is_animation_blacklisted(1));
6373 assert!(model.get_extended_particle_data().is_none());
6374 }
6375
6376 #[test]
6377 #[allow(clippy::field_reassign_with_default)]
6378 fn test_m2_write_read_roundtrip() {
6379 use crate::chunks::vertex::M2Vertex;
6380 use crate::common::{C2Vector, C3Vector};
6381 use crate::version::M2Version;
6382
6383 let mut model = M2Model::default();
6385 model.header = M2Header::new(M2Version::WotLK);
6386 model.name = Some("TestModel".to_string());
6387
6388 for i in 0..4 {
6390 let vertex = M2Vertex {
6391 position: C3Vector {
6392 x: i as f32,
6393 y: 0.0,
6394 z: 0.0,
6395 },
6396 bone_weights: [255, 0, 0, 0],
6397 bone_indices: [0, 0, 0, 0],
6398 normal: C3Vector {
6399 x: 0.0,
6400 y: 1.0,
6401 z: 0.0,
6402 },
6403 tex_coords: C2Vector { x: 0.0, y: 0.0 },
6404 tex_coords2: None,
6405 };
6406 model.vertices.push(vertex);
6407 }
6408
6409 let mut buffer = Cursor::new(Vec::new());
6411 let write_result = model.write(&mut buffer);
6412 assert!(write_result.is_ok(), "Write should succeed");
6413
6414 buffer.set_position(0);
6416 let read_result = M2Model::parse(&mut buffer);
6417 assert!(read_result.is_ok(), "Read should succeed");
6418
6419 let read_model = read_result.unwrap();
6420
6421 assert_eq!(read_model.name, model.name, "Name should match");
6423 assert_eq!(
6424 read_model.vertices.len(),
6425 model.vertices.len(),
6426 "Vertex count should match"
6427 );
6428 assert_eq!(
6429 read_model.header.version, model.header.version,
6430 "Version should match"
6431 );
6432
6433 for (i, (orig, read)) in model
6435 .vertices
6436 .iter()
6437 .zip(read_model.vertices.iter())
6438 .enumerate()
6439 {
6440 assert_eq!(
6441 orig.position.x, read.position.x,
6442 "Vertex {} X position should match",
6443 i
6444 );
6445 assert_eq!(
6446 orig.position.y, read.position.y,
6447 "Vertex {} Y position should match",
6448 i
6449 );
6450 assert_eq!(
6451 orig.position.z, read.position.z,
6452 "Vertex {} Z position should match",
6453 i
6454 );
6455 }
6456 }
6457
6458 #[test]
6459 #[allow(clippy::field_reassign_with_default)]
6460 fn test_m2_version_conversion_roundtrip() {
6461 use crate::chunks::vertex::M2Vertex;
6462 use crate::common::{C2Vector, C3Vector};
6463 use crate::version::M2Version;
6464
6465 let mut model = M2Model::default();
6467 model.header = M2Header::new(M2Version::WotLK);
6468 model.name = Some("ConversionTest".to_string());
6469
6470 let vertex = M2Vertex {
6472 position: C3Vector {
6473 x: 1.0,
6474 y: 2.0,
6475 z: 3.0,
6476 },
6477 bone_weights: [255, 0, 0, 0],
6478 bone_indices: [0, 0, 0, 0],
6479 normal: C3Vector {
6480 x: 0.0,
6481 y: 1.0,
6482 z: 0.0,
6483 },
6484 tex_coords: C2Vector { x: 0.5, y: 0.5 },
6485 tex_coords2: None,
6486 };
6487 model.vertices.push(vertex);
6488
6489 let convert_result = model.convert(M2Version::TBC);
6491 assert!(convert_result.is_ok(), "Conversion to TBC should succeed");
6492 let tbc_model = convert_result.unwrap();
6493
6494 assert_eq!(
6496 tbc_model.header.version,
6497 M2Version::TBC.to_header_version(),
6498 "Version should be TBC"
6499 );
6500
6501 let mut buffer = Cursor::new(Vec::new());
6503 let write_result = tbc_model.write(&mut buffer);
6504 assert!(
6505 write_result.is_ok(),
6506 "Write of converted model should succeed"
6507 );
6508
6509 buffer.set_position(0);
6511 let read_result = M2Model::parse(&mut buffer);
6512 assert!(
6513 read_result.is_ok(),
6514 "Read of converted model should succeed: {:?}",
6515 read_result.err()
6516 );
6517
6518 let read_model = read_result.unwrap();
6519 assert_eq!(
6520 read_model.header.version,
6521 M2Version::TBC.to_header_version(),
6522 "Re-read version should be TBC"
6523 );
6524 assert_eq!(read_model.vertices.len(), 1, "Should have 1 vertex");
6525 }
6526
6527 #[test]
6528 #[allow(clippy::field_reassign_with_default)]
6529 fn test_m2_cataclysm_roundtrip() {
6530 use crate::chunks::vertex::M2Vertex;
6531 use crate::common::{C2Vector, C3Vector};
6532 use crate::version::M2Version;
6533
6534 let mut model = M2Model::default();
6536 model.header = M2Header::new(M2Version::Cataclysm);
6537 model.name = Some("CataModel".to_string());
6538
6539 let vertex = M2Vertex {
6541 position: C3Vector {
6542 x: 1.0,
6543 y: 2.0,
6544 z: 3.0,
6545 },
6546 bone_weights: [255, 0, 0, 0],
6547 bone_indices: [0, 0, 0, 0],
6548 normal: C3Vector {
6549 x: 0.0,
6550 y: 1.0,
6551 z: 0.0,
6552 },
6553 tex_coords: C2Vector { x: 0.5, y: 0.5 },
6554 tex_coords2: Some(C2Vector { x: 0.25, y: 0.75 }),
6555 };
6556 model.vertices.push(vertex);
6557
6558 let mut buffer = Cursor::new(Vec::new());
6560 let write_result = model.write(&mut buffer);
6561 assert!(
6562 write_result.is_ok(),
6563 "Write of Cataclysm model should succeed: {:?}",
6564 write_result.err()
6565 );
6566
6567 buffer.set_position(0);
6569 let read_result = M2Model::parse(&mut buffer);
6570 assert!(
6571 read_result.is_ok(),
6572 "Read of Cataclysm model should succeed: {:?}",
6573 read_result.err()
6574 );
6575
6576 let read_model = read_result.unwrap();
6577 assert_eq!(
6578 read_model.header.version,
6579 M2Version::Cataclysm.to_header_version(),
6580 "Re-read version should be Cataclysm (272)"
6581 );
6582 assert_eq!(read_model.vertices.len(), 1, "Should have 1 vertex");
6583
6584 assert!(
6586 read_model.vertices[0].tex_coords2.is_some(),
6587 "Cataclysm should have secondary tex coords"
6588 );
6589 }
6590
6591 #[test]
6592 #[allow(clippy::field_reassign_with_default)]
6593 fn test_m2_mop_roundtrip() {
6594 use crate::chunks::vertex::M2Vertex;
6595 use crate::common::{C2Vector, C3Vector};
6596 use crate::version::M2Version;
6597
6598 let mut model = M2Model::default();
6600 model.header = M2Header::new(M2Version::MoP);
6601 model.name = Some("MoPModel".to_string());
6602
6603 for i in 0..3 {
6605 let vertex = M2Vertex {
6606 position: C3Vector {
6607 x: i as f32,
6608 y: 0.0,
6609 z: 0.0,
6610 },
6611 bone_weights: [255, 0, 0, 0],
6612 bone_indices: [0, 0, 0, 0],
6613 normal: C3Vector {
6614 x: 0.0,
6615 y: 1.0,
6616 z: 0.0,
6617 },
6618 tex_coords: C2Vector { x: 0.0, y: 0.0 },
6619 tex_coords2: Some(C2Vector { x: 1.0, y: 1.0 }),
6620 };
6621 model.vertices.push(vertex);
6622 }
6623
6624 let mut buffer = Cursor::new(Vec::new());
6626 let write_result = model.write(&mut buffer);
6627 assert!(
6628 write_result.is_ok(),
6629 "Write of MoP model should succeed: {:?}",
6630 write_result.err()
6631 );
6632
6633 buffer.set_position(0);
6635 let read_result = M2Model::parse(&mut buffer);
6636 assert!(
6637 read_result.is_ok(),
6638 "Read of MoP model should succeed: {:?}",
6639 read_result.err()
6640 );
6641
6642 let read_model = read_result.unwrap();
6643 assert_eq!(
6644 read_model.header.version,
6645 M2Version::MoP.to_header_version(),
6646 "Re-read version should be MoP (272)"
6647 );
6648 assert_eq!(read_model.vertices.len(), 3, "Should have 3 vertices");
6649 }
6650
6651 #[test]
6652 #[allow(clippy::field_reassign_with_default)]
6653 fn test_m2_wotlk_to_cataclysm_conversion() {
6654 use crate::chunks::vertex::M2Vertex;
6655 use crate::common::{C2Vector, C3Vector};
6656 use crate::version::M2Version;
6657
6658 let mut model = M2Model::default();
6660 model.header = M2Header::new(M2Version::WotLK);
6661 model.name = Some("WotLKToCata".to_string());
6662
6663 let vertex = M2Vertex {
6664 position: C3Vector {
6665 x: 1.0,
6666 y: 2.0,
6667 z: 3.0,
6668 },
6669 bone_weights: [255, 0, 0, 0],
6670 bone_indices: [0, 0, 0, 0],
6671 normal: C3Vector {
6672 x: 0.0,
6673 y: 1.0,
6674 z: 0.0,
6675 },
6676 tex_coords: C2Vector { x: 0.5, y: 0.5 },
6677 tex_coords2: None,
6678 };
6679 model.vertices.push(vertex);
6680
6681 let converted = model
6683 .convert(M2Version::Cataclysm)
6684 .expect("WotLK -> Cataclysm conversion failed");
6685
6686 assert_eq!(
6687 converted.header.version,
6688 M2Version::Cataclysm.to_header_version(),
6689 "Version should be Cataclysm (272)"
6690 );
6691
6692 let mut buffer = Cursor::new(Vec::new());
6694 converted.write(&mut buffer).expect("Write failed");
6695
6696 buffer.set_position(0);
6697 let read_model = M2Model::parse(&mut buffer).expect("Read failed");
6698
6699 assert_eq!(
6700 read_model.header.version,
6701 M2Version::Cataclysm.to_header_version()
6702 );
6703 assert_eq!(read_model.vertices.len(), 1);
6704 }
6705
6706 #[test]
6707 #[allow(clippy::field_reassign_with_default)]
6708 fn test_m2_wotlk_to_mop_conversion() {
6709 use crate::chunks::vertex::M2Vertex;
6710 use crate::common::{C2Vector, C3Vector};
6711 use crate::version::M2Version;
6712
6713 let mut model = M2Model::default();
6714 model.header = M2Header::new(M2Version::WotLK);
6715 model.name = Some("WotLKToMoP".to_string());
6716
6717 let vertex = M2Vertex {
6718 position: C3Vector {
6719 x: 1.0,
6720 y: 2.0,
6721 z: 3.0,
6722 },
6723 bone_weights: [255, 0, 0, 0],
6724 bone_indices: [0, 0, 0, 0],
6725 normal: C3Vector {
6726 x: 0.0,
6727 y: 1.0,
6728 z: 0.0,
6729 },
6730 tex_coords: C2Vector { x: 0.5, y: 0.5 },
6731 tex_coords2: None,
6732 };
6733 model.vertices.push(vertex);
6734
6735 let converted = model
6737 .convert(M2Version::MoP)
6738 .expect("WotLK -> MoP conversion failed");
6739
6740 assert_eq!(converted.header.version, M2Version::MoP.to_header_version());
6741
6742 let mut buffer = Cursor::new(Vec::new());
6744 converted.write(&mut buffer).expect("Write failed");
6745
6746 buffer.set_position(0);
6747 let read_model = M2Model::parse(&mut buffer).expect("Read failed");
6748
6749 assert_eq!(
6750 read_model.header.version,
6751 M2Version::MoP.to_header_version()
6752 );
6753 assert_eq!(read_model.vertices.len(), 1);
6754 }
6755
6756 #[test]
6757 #[allow(clippy::field_reassign_with_default)]
6758 fn test_m2_wotlk_to_vanilla_conversion() {
6759 use crate::chunks::vertex::M2Vertex;
6760 use crate::common::{C2Vector, C3Vector};
6761 use crate::version::M2Version;
6762
6763 let mut model = M2Model::default();
6764 model.header = M2Header::new(M2Version::WotLK);
6765 model.name = Some("WotLKToVanilla".to_string());
6766
6767 let vertex = M2Vertex {
6768 position: C3Vector {
6769 x: 1.0,
6770 y: 2.0,
6771 z: 3.0,
6772 },
6773 bone_weights: [255, 0, 0, 0],
6774 bone_indices: [0, 0, 0, 0],
6775 normal: C3Vector {
6776 x: 0.0,
6777 y: 1.0,
6778 z: 0.0,
6779 },
6780 tex_coords: C2Vector { x: 0.5, y: 0.5 },
6781 tex_coords2: None,
6782 };
6783 model.vertices.push(vertex);
6784
6785 let converted = model
6787 .convert(M2Version::Vanilla)
6788 .expect("WotLK -> Vanilla conversion failed");
6789
6790 assert_eq!(
6791 converted.header.version,
6792 M2Version::Vanilla.to_header_version()
6793 );
6794
6795 let mut buffer = Cursor::new(Vec::new());
6797 converted.write(&mut buffer).expect("Write failed");
6798
6799 buffer.set_position(0);
6800 let read_model = M2Model::parse(&mut buffer).expect("Read failed");
6801
6802 assert_eq!(
6803 read_model.header.version,
6804 M2Version::Vanilla.to_header_version()
6805 );
6806 assert_eq!(read_model.vertices.len(), 1);
6807 }
6808
6809 #[test]
6810 #[allow(clippy::field_reassign_with_default)]
6811 fn test_m2_cataclysm_to_wotlk_conversion() {
6812 use crate::chunks::vertex::M2Vertex;
6813 use crate::common::{C2Vector, C3Vector};
6814 use crate::version::M2Version;
6815
6816 let mut model = M2Model::default();
6817 model.header = M2Header::new(M2Version::Cataclysm);
6818 model.name = Some("CataToWotLK".to_string());
6819
6820 let vertex = M2Vertex {
6821 position: C3Vector {
6822 x: 1.0,
6823 y: 2.0,
6824 z: 3.0,
6825 },
6826 bone_weights: [255, 0, 0, 0],
6827 bone_indices: [0, 0, 0, 0],
6828 normal: C3Vector {
6829 x: 0.0,
6830 y: 1.0,
6831 z: 0.0,
6832 },
6833 tex_coords: C2Vector { x: 0.5, y: 0.5 },
6834 tex_coords2: Some(C2Vector { x: 0.25, y: 0.75 }),
6835 };
6836 model.vertices.push(vertex);
6837
6838 let converted = model
6840 .convert(M2Version::WotLK)
6841 .expect("Cataclysm -> WotLK conversion failed");
6842
6843 assert_eq!(
6844 converted.header.version,
6845 M2Version::WotLK.to_header_version()
6846 );
6847
6848 let mut buffer = Cursor::new(Vec::new());
6850 converted.write(&mut buffer).expect("Write failed");
6851
6852 buffer.set_position(0);
6853 let read_model = M2Model::parse(&mut buffer).expect("Read failed");
6854
6855 assert_eq!(
6856 read_model.header.version,
6857 M2Version::WotLK.to_header_version()
6858 );
6859 assert_eq!(read_model.vertices.len(), 1);
6860
6861 assert!(read_model.vertices[0].tex_coords2.is_some());
6864 }
6865}