1use crate::io_ext::{ReadExt, WriteExt};
2use std::io::{Read, Write};
3
4use crate::chunks::m2_track::{M2TrackQuat, M2TrackVec3};
5use crate::common::C3Vector;
6use crate::error::Result;
7use crate::version::M2Version;
8
9bitflags::bitflags! {
10 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12 pub struct M2BoneFlags: u32 {
13 const SPHERICAL_BILLBOARD = 0x8;
15 const CYLINDRICAL_BILLBOARD_LOCK_X = 0x10;
17 const CYLINDRICAL_BILLBOARD_LOCK_Y = 0x20;
19 const CYLINDRICAL_BILLBOARD_LOCK_Z = 0x40;
21 const TRANSFORMED = 0x200;
23 const KINEMATIC_BONE = 0x400;
25 const HELPER_BONE = 0x1000;
27 const HAS_ANIMATION = 0x4000;
29 const ANIMATED_AT_HIGHER_LODS = 0x8000;
31 const HAS_PROCEDURAL_ANIMATION = 0x10000;
33 const HAS_IK = 0x20000;
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct M2Bone {
41 pub bone_id: i32,
43 pub flags: M2BoneFlags,
45 pub parent_bone: i16,
47 pub submesh_id: u16,
49 pub unknown: [u16; 2],
51 pub bone_name_crc: Option<u32>,
54 pub translation: M2TrackVec3,
56 pub rotation: M2TrackQuat,
58 pub scale: M2TrackVec3,
60 pub pivot: C3Vector,
62}
63
64impl M2Bone {
65 pub fn parse<R: Read>(reader: &mut R, version: u32) -> Result<Self> {
67 let bone_id = reader.read_i32_le()?;
69 let flags = M2BoneFlags::from_bits_retain(reader.read_u32_le()?);
70 let parent_bone = reader.read_i16_le()?;
71 let submesh_id = reader.read_u16_le()?;
72
73 let (unknown, bone_name_crc) = if version >= 260 {
77 let bone_name_crc = reader.read_u32_le()?;
79 ([0, 0], Some(bone_name_crc)) } else {
81 ([0, 0], None) };
84
85 let translation = M2TrackVec3::parse(reader, version)?;
86 let rotation = M2TrackQuat::parse(reader, version)?;
87 let scale = M2TrackVec3::parse(reader, version)?;
88
89 let mut pivot = C3Vector::parse(reader)?;
90
91 if pivot.x.is_nan() || pivot.y.is_nan() || pivot.z.is_nan() {
94 if pivot.x.is_nan() {
96 pivot.x = 0.0;
97 }
98 if pivot.y.is_nan() {
99 pivot.y = 0.0;
100 }
101 if pivot.z.is_nan() {
102 pivot.z = 0.0;
103 }
104 }
105
106 Ok(Self {
107 bone_id,
108 flags,
109 parent_bone,
110 submesh_id,
111 unknown,
112 bone_name_crc,
113 translation,
114 rotation,
115 scale,
116 pivot,
117 })
118 }
119
120 pub fn write<W: Write>(&self, writer: &mut W, version: u32) -> Result<()> {
122 writer.write_i32_le(self.bone_id)?;
123 writer.write_u32_le(self.flags.bits())?;
124 writer.write_i16_le(self.parent_bone)?;
125 writer.write_u16_le(self.submesh_id)?;
126
127 if version >= 260 {
128 writer.write_u32_le(self.bone_name_crc.unwrap_or(0))?; } else {
131 }
134
135 self.translation.write(writer, version)?;
136 self.rotation.write(writer, version)?;
137 self.scale.write(writer, version)?;
138
139 self.pivot.write(writer)?;
140
141 Ok(())
142 }
143
144 pub fn convert(&self, _target_version: M2Version) -> Self {
146 self.clone()
147 }
148
149 pub fn new(bone_id: i32, parent_bone: i16) -> Self {
151 Self {
152 bone_id,
153 flags: M2BoneFlags::empty(),
154 parent_bone,
155 submesh_id: 0,
156 unknown: [0, 0],
157 bone_name_crc: None,
158 translation: M2TrackVec3::new(),
159 rotation: M2TrackQuat::new(),
160 scale: M2TrackVec3::new(),
161 pivot: C3Vector {
162 x: 0.0,
163 y: 0.0,
164 z: 0.0,
165 },
166 }
167 }
168
169 pub fn is_valid_for_model(&self, total_bone_count: u32) -> bool {
172 if self.bone_id < -1 || self.bone_id > 1000 {
174 return false;
175 }
176
177 if self.parent_bone != -1
179 && (self.parent_bone < 0 || self.parent_bone as u32 >= total_bone_count)
180 {
181 return false;
182 }
183
184 if self.translation.timestamps.count > 100_000 || self.translation.values.count > 100_000 {
186 return false;
187 }
188
189 if self.rotation.timestamps.count > 100_000 || self.rotation.values.count > 100_000 {
190 return false;
191 }
192
193 if self.scale.timestamps.count > 100_000 || self.scale.values.count > 100_000 {
194 return false;
195 }
196
197 true
198 }
199
200 pub fn debug_info(&self) -> String {
202 let crc_info = if let Some(crc) = self.bone_name_crc {
203 format!(", name_crc=0x{:08x}", crc)
204 } else {
205 String::new()
206 };
207 format!(
208 "Bone(id={}, parent={}, flags=0x{:x}{}, trans_count={}, rot_count={}, scale_count={})",
209 self.bone_id,
210 self.parent_bone,
211 self.flags.bits(),
212 crc_info,
213 self.translation.timestamps.count,
214 self.rotation.timestamps.count,
215 self.scale.timestamps.count
216 )
217 }
218
219 pub fn get_bone_name_crc(&self) -> Option<u32> {
221 self.bone_name_crc
222 }
223
224 pub fn is_billboard(&self) -> bool {
226 self.flags.contains(M2BoneFlags::SPHERICAL_BILLBOARD)
227 || self
228 .flags
229 .contains(M2BoneFlags::CYLINDRICAL_BILLBOARD_LOCK_X)
230 || self
231 .flags
232 .contains(M2BoneFlags::CYLINDRICAL_BILLBOARD_LOCK_Y)
233 || self
234 .flags
235 .contains(M2BoneFlags::CYLINDRICAL_BILLBOARD_LOCK_Z)
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use std::io::Cursor;
243
244 #[test]
245 fn test_bone_parse() {
246 let mut data = Vec::new();
247
248 data.extend_from_slice(&1i32.to_le_bytes());
250
251 data.extend_from_slice(&0x200u32.to_le_bytes());
253
254 data.extend_from_slice(&(-1i16).to_le_bytes());
256
257 data.extend_from_slice(&0u16.to_le_bytes());
259
260 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0.0f32.to_le_bytes());
294 data.extend_from_slice(&0.0f32.to_le_bytes());
295 data.extend_from_slice(&0.0f32.to_le_bytes());
296
297 let mut cursor = Cursor::new(data);
298 let bone = M2Bone::parse(&mut cursor, M2Version::Vanilla.to_header_version()).unwrap();
299
300 assert_eq!(bone.bone_id, 1);
301 assert_eq!(bone.flags, M2BoneFlags::TRANSFORMED);
302 assert_eq!(bone.parent_bone, -1);
303 assert_eq!(bone.submesh_id, 0);
304 assert!(!bone.translation.has_data()); assert!(!bone.rotation.has_data()); assert!(!bone.scale.has_data()); }
309
310 #[test]
311 fn test_bone_validation_constraints() {
312 let mut data = Vec::new();
314
315 data.extend_from_slice(&1i32.to_le_bytes()); data.extend_from_slice(&0x200u32.to_le_bytes()); data.extend_from_slice(&0i16.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); for _ in 0..3 {
324 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&5u32.to_le_bytes()); data.extend_from_slice(&1000u32.to_le_bytes()); data.extend_from_slice(&5u32.to_le_bytes()); data.extend_from_slice(&1200u32.to_le_bytes()); }
334
335 data.extend_from_slice(&0.0f32.to_le_bytes()); data.extend_from_slice(&1.0f32.to_le_bytes()); data.extend_from_slice(&0.0f32.to_le_bytes()); data.extend_from_slice(&1000i32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&50i16.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); for count in [4294901760u32, 22768u32, 100u32] {
348 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&count.to_le_bytes()); data.extend_from_slice(&1000u32.to_le_bytes()); data.extend_from_slice(&count.to_le_bytes()); data.extend_from_slice(&1200u32.to_le_bytes()); }
358
359 data.extend_from_slice(&0.0f32.to_le_bytes()); data.extend_from_slice(&1.0f32.to_le_bytes()); data.extend_from_slice(&0.0f32.to_le_bytes()); println!(
364 "Test data created: {} bytes (2 bones * 108 = 216 expected)",
365 data.len()
366 );
367 assert_eq!(data.len(), 216); let mut cursor = Cursor::new(&data);
370
371 let bone1 = M2Bone::parse(&mut cursor, 256).unwrap();
373
374 println!(
375 "Bone 1: id={}, parent={}, translation_count={}",
376 bone1.bone_id, bone1.parent_bone, bone1.translation.timestamps.count
377 );
378
379 assert_eq!(bone1.bone_id, 1);
381 assert_eq!(bone1.parent_bone, 0);
382 assert_eq!(bone1.translation.timestamps.count, 5);
383 assert_eq!(bone1.rotation.timestamps.count, 5);
384 assert_eq!(bone1.scale.timestamps.count, 5);
385
386 let bone2 = M2Bone::parse(&mut cursor, 256).unwrap();
388
389 println!(
390 "Bone 2: id={}, parent={}, translation_count={}",
391 bone2.bone_id, bone2.parent_bone, bone2.translation.timestamps.count
392 );
393
394 assert_eq!(bone2.bone_id, 1000);
396 assert_eq!(bone2.parent_bone, 50);
397 assert_eq!(bone2.translation.timestamps.count, 4294901760);
398 assert_eq!(bone2.rotation.timestamps.count, 22768);
399 assert_eq!(bone2.scale.timestamps.count, 100);
400
401 assert_eq!(cursor.position(), 216);
403
404 println!(
405 "✓ Both bones parsed with expected values (including intentionally corrupted second bone)"
406 );
407 println!("This confirms M2Bone::parse correctly reads what's in the data,");
408 println!("so the issue must be in the source data or cursor positioning.");
409 }
410
411 #[test]
412 fn test_m2track_byte_consumption_vanilla() {
413 let mut data = Vec::new();
415
416 data.extend_from_slice(&1u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&800u32.to_le_bytes()); data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&1000u32.to_le_bytes()); data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&1200u32.to_le_bytes()); assert_eq!(
427 data.len(),
428 28,
429 "M2Track test data should be exactly 28 bytes for Vanilla"
430 );
431
432 let mut cursor = Cursor::new(&data);
433 let pos_before = cursor.position();
434
435 let track = M2TrackVec3::parse(&mut cursor, 256).unwrap();
437
438 let pos_after = cursor.position();
439 let bytes_consumed = pos_after - pos_before;
440
441 println!(
442 "M2Track parsing: consumed {} bytes (expected 28)",
443 bytes_consumed
444 );
445 println!(
446 "Track details: interp={:?}, timestamps_count={}, values_count={}",
447 track.base.interpolation_type, track.timestamps.count, track.values.count
448 );
449
450 assert_eq!(
452 bytes_consumed, 28,
453 "M2Track should consume exactly 28 bytes for version 256, but consumed {}",
454 bytes_consumed
455 );
456
457 assert!(
459 track.ranges.is_some(),
460 "M2Track SHOULD have ranges field for version 256 (Vanilla)"
461 );
462
463 println!("✓ M2Track consumes exactly 28 bytes as expected for Vanilla");
464 }
465
466 #[test]
467 fn test_m2track_byte_consumption_tbc() {
468 let mut data = Vec::new();
470
471 data.extend_from_slice(&1u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&1000u32.to_le_bytes()); data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&1200u32.to_le_bytes()); assert_eq!(
482 data.len(),
483 28,
484 "M2Track test data should be exactly 28 bytes for TBC"
485 );
486
487 let mut cursor = Cursor::new(&data);
488 let pos_before = cursor.position();
489
490 let track = M2TrackVec3::parse(&mut cursor, 260).unwrap();
492
493 let pos_after = cursor.position();
494 let bytes_consumed = pos_after - pos_before;
495
496 println!(
497 "M2Track parsing: consumed {} bytes (expected 28)",
498 bytes_consumed
499 );
500 println!(
501 "Track details: interp={:?}, timestamps_count={}, values_count={}",
502 track.base.interpolation_type, track.timestamps.count, track.values.count
503 );
504
505 assert_eq!(
507 bytes_consumed, 28,
508 "M2Track should consume exactly 28 bytes for version 260, but consumed {}",
509 bytes_consumed
510 );
511
512 assert!(
514 track.ranges.is_some(),
515 "M2Track should have ranges field for version 260 (TBC)"
516 );
517
518 println!("✓ M2Track consumes exactly 28 bytes as expected for TBC");
519 }
520
521 #[test]
522 fn test_sequential_bone_parsing() {
523 let mut data = Vec::new();
525
526 for bone_id in 0i32..3 {
528 data.extend_from_slice(&bone_id.to_le_bytes());
530
531 data.extend_from_slice(&0x200u32.to_le_bytes());
533
534 let parent = if bone_id == 0 { -1 } else { bone_id - 1 };
536 data.extend_from_slice(&(parent as i16).to_le_bytes());
537
538 data.extend_from_slice(&0u16.to_le_bytes());
540
541 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0.0f32.to_le_bytes());
575 data.extend_from_slice(&0.0f32.to_le_bytes());
576 data.extend_from_slice(&0.0f32.to_le_bytes());
577 }
578
579 println!(
580 "Total test data: {} bytes (expected: 3 * 108 = 324)",
581 data.len()
582 );
583 assert_eq!(data.len(), 324); let mut cursor = Cursor::new(data);
586
587 let mut bones = Vec::new();
589 for i in 0..3 {
590 let pos_before = cursor.position();
591 let bone = M2Bone::parse(&mut cursor, M2Version::Vanilla.to_header_version()).unwrap();
592 let pos_after = cursor.position();
593 let bytes_consumed = pos_after - pos_before;
594
595 println!(
596 "Bone {}: id={}, parent={}, consumed {} bytes (pos: {} -> {})",
597 i, bone.bone_id, bone.parent_bone, bytes_consumed, pos_before, pos_after
598 );
599
600 assert_eq!(bone.bone_id, i);
602 assert_eq!(bone.flags, M2BoneFlags::TRANSFORMED);
603 if i == 0 {
604 assert_eq!(bone.parent_bone, -1);
605 } else {
606 assert_eq!(bone.parent_bone, (i - 1) as i16);
607 }
608 assert_eq!(bone.submesh_id, 0);
609
610 assert_eq!(
613 bytes_consumed, 108,
614 "Bone {} consumed {} bytes, expected 108",
615 i, bytes_consumed
616 );
617
618 bones.push(bone);
619 }
620
621 assert_eq!(cursor.position(), 324);
623 println!("✓ All 3 bones parsed successfully with correct sequential alignment");
624 }
625
626 #[test]
627 fn test_nan_pivot_fix() {
628 let mut data = Vec::new();
630
631 data.extend_from_slice(&1i32.to_le_bytes());
633
634 data.extend_from_slice(&0x200u32.to_le_bytes());
636
637 data.extend_from_slice(&(-1i16).to_le_bytes());
639
640 data.extend_from_slice(&0u16.to_le_bytes());
642
643 for _ in 0..3 {
645 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&65535u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); }
655
656 data.extend_from_slice(&f32::NAN.to_le_bytes()); data.extend_from_slice(&2.5f32.to_le_bytes()); data.extend_from_slice(&f32::NAN.to_le_bytes()); let mut cursor = Cursor::new(data);
662 let bone = M2Bone::parse(&mut cursor, M2Version::Vanilla.to_header_version()).unwrap();
663
664 assert_eq!(bone.pivot.x, 0.0, "NaN pivot.x should be replaced with 0.0");
666 assert_eq!(bone.pivot.y, 2.5, "Valid pivot.y should be preserved");
667 assert_eq!(bone.pivot.z, 0.0, "NaN pivot.z should be replaced with 0.0");
668
669 assert!(
671 !bone.pivot.x.is_nan(),
672 "pivot.x should not be NaN after parsing"
673 );
674 assert!(!bone.pivot.y.is_nan(), "pivot.y should not be NaN");
675 assert!(
676 !bone.pivot.z.is_nan(),
677 "pivot.z should not be NaN after parsing"
678 );
679
680 assert_eq!(bone.bone_id, 1);
682 assert_eq!(bone.parent_bone, -1);
683
684 println!(
685 "✓ NaN pivot coordinates fixed: (NaN, 2.5, NaN) -> ({}, {}, {})",
686 bone.pivot.x, bone.pivot.y, bone.pivot.z
687 );
688 }
689
690 #[test]
691 fn test_bone_write() {
692 let bone = M2Bone {
693 bone_id: 1,
694 flags: M2BoneFlags::TRANSFORMED,
695 parent_bone: -1,
696 submesh_id: 0,
697 unknown: [0, 0],
698 bone_name_crc: Some(0x12345678), translation: M2TrackVec3::new(),
700 rotation: M2TrackQuat::new(),
701 scale: M2TrackVec3::new(),
702 pivot: C3Vector {
703 x: 0.0,
704 y: 0.0,
705 z: 0.0,
706 },
707 };
708
709 let mut data = Vec::new();
710 bone.write(&mut data, 260).unwrap(); assert_eq!(data.len(), 112);
715
716 let mut vanilla_data = Vec::new();
718 bone.write(&mut vanilla_data, 256).unwrap(); assert_eq!(vanilla_data.len(), 108);
722 }
723
724 #[test]
725 fn test_bone_write_wotlk() {
726 let bone = M2Bone {
727 bone_id: 1,
728 flags: M2BoneFlags::TRANSFORMED,
729 parent_bone: -1,
730 submesh_id: 0,
731 unknown: [0, 0],
732 bone_name_crc: Some(0x12345678),
733 translation: M2TrackVec3::new(),
734 rotation: M2TrackQuat::new(),
735 scale: M2TrackVec3::new(),
736 pivot: C3Vector {
737 x: 0.0,
738 y: 0.0,
739 z: 0.0,
740 },
741 };
742
743 let mut data = Vec::new();
744 bone.write(&mut data, 264).unwrap(); println!("WotLK bone write size: {} bytes (expected 88)", data.len());
747 assert_eq!(
748 data.len(),
749 88,
750 "WotLK bone should be 88 bytes, got {}",
751 data.len()
752 );
753 }
754}