1use std::io::{Read, Seek};
7
8use crate::chunks::infrastructure::ChunkReader;
9use crate::error::Result;
10use crate::io_ext::ReadExt;
11
12#[derive(Debug, Clone)]
15pub struct SkinFileIds {
16 pub ids: Vec<u32>,
18}
19
20impl SkinFileIds {
21 pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
23 let count = reader.chunk_size() / 4; let mut ids = Vec::with_capacity(count as usize);
25
26 for _ in 0..count {
27 ids.push(reader.read_u32_le()?);
28 }
29
30 Ok(SkinFileIds { ids })
31 }
32
33 pub fn len(&self) -> usize {
35 self.ids.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
40 self.ids.is_empty()
41 }
42
43 pub fn get(&self, index: usize) -> Option<u32> {
45 self.ids.get(index).copied()
46 }
47
48 pub fn iter(&self) -> std::slice::Iter<'_, u32> {
50 self.ids.iter()
51 }
52}
53
54#[derive(Debug, Clone)]
57pub struct AnimationFileIds {
58 pub ids: Vec<u32>,
60}
61
62impl AnimationFileIds {
63 pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
65 let count = reader.chunk_size() / 4; let mut ids = Vec::with_capacity(count as usize);
67
68 for _ in 0..count {
69 ids.push(reader.read_u32_le()?);
70 }
71
72 Ok(AnimationFileIds { ids })
73 }
74
75 pub fn len(&self) -> usize {
77 self.ids.len()
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.ids.is_empty()
83 }
84
85 pub fn get(&self, index: usize) -> Option<u32> {
87 self.ids.get(index).copied()
88 }
89
90 pub fn iter(&self) -> std::slice::Iter<'_, u32> {
92 self.ids.iter()
93 }
94}
95
96#[derive(Debug, Clone)]
99pub struct TextureFileIds {
100 pub ids: Vec<u32>,
102}
103
104#[derive(Debug, Clone)]
107pub struct PhysicsFileId {
108 pub id: u32,
110}
111
112#[derive(Debug, Clone)]
115pub struct SkeletonFileId {
116 pub id: u32,
118}
119
120#[derive(Debug, Clone)]
123pub struct BoneFileIds {
124 pub ids: Vec<u32>,
126}
127
128#[derive(Debug, Clone)]
131pub struct LodData {
132 pub levels: Vec<LodLevel>,
134}
135
136#[derive(Debug, Clone)]
138pub struct LodLevel {
139 pub distance: f32,
141 pub skin_file_index: u16,
143 pub vertex_count: u32,
145 pub triangle_count: u32,
147}
148
149#[derive(Debug, Clone)]
151pub struct PhysicsData {
152 pub collision_mesh: Option<CollisionMesh>,
154 pub material_properties: Vec<PhysicsMaterial>,
156}
157
158#[derive(Debug, Clone)]
160pub struct CollisionMesh {
161 pub vertices: Vec<[f32; 3]>,
163 pub triangles: Vec<[u16; 3]>,
165}
166
167#[derive(Debug, Clone)]
169pub struct PhysicsMaterial {
170 pub material_id: u32,
172 pub friction: f32,
174 pub restitution: f32,
176 pub density: f32,
178}
179
180#[derive(Debug, Clone)]
182pub struct SkeletonData {
183 pub bone_hierarchy: Vec<BoneNode>,
185 pub root_bones: Vec<u16>,
187}
188
189#[derive(Debug, Clone)]
191pub struct BoneNode {
192 pub bone_index: u16,
194 pub parent_index: i16,
196 pub children: Vec<u16>,
198 pub local_transform: [f32; 16], }
201
202#[derive(Debug, Clone)]
204pub struct BoneData {
205 pub bone_index: u16,
207 pub translation_track: AnimationTrack<[f32; 3]>,
209 pub rotation_track: AnimationTrack<[f32; 4]>,
211 pub scale_track: AnimationTrack<[f32; 3]>,
213}
214
215#[derive(Debug, Clone)]
217pub struct AnimationTrack<T> {
218 pub timestamps: Vec<u32>,
220 pub values: Vec<T>,
222 pub interpolation: InterpolationType,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum InterpolationType {
229 None,
231 Linear,
233 Bezier,
235}
236
237impl TextureFileIds {
238 pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
240 let count = reader.chunk_size() / 4; let mut ids = Vec::with_capacity(count as usize);
242
243 for _ in 0..count {
244 ids.push(reader.read_u32_le()?);
245 }
246
247 Ok(TextureFileIds { ids })
248 }
249
250 pub fn len(&self) -> usize {
252 self.ids.len()
253 }
254
255 pub fn is_empty(&self) -> bool {
257 self.ids.is_empty()
258 }
259
260 pub fn get(&self, index: usize) -> Option<u32> {
262 self.ids.get(index).copied()
263 }
264
265 pub fn iter(&self) -> std::slice::Iter<'_, u32> {
267 self.ids.iter()
268 }
269}
270
271impl PhysicsFileId {
272 pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
274 if reader.chunk_size() != 4 {
275 return Err(crate::error::M2Error::ParseError(format!(
276 "PFID chunk should contain exactly 4 bytes, got {}",
277 reader.chunk_size()
278 )));
279 }
280
281 let id = reader.read_u32_le()?;
282 Ok(PhysicsFileId { id })
283 }
284}
285
286impl SkeletonFileId {
287 pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
289 if reader.chunk_size() != 4 {
290 return Err(crate::error::M2Error::ParseError(format!(
291 "SKID chunk should contain exactly 4 bytes, got {}",
292 reader.chunk_size()
293 )));
294 }
295
296 let id = reader.read_u32_le()?;
297 Ok(SkeletonFileId { id })
298 }
299}
300
301impl BoneFileIds {
302 pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
304 let count = reader.chunk_size() / 4; let mut ids = Vec::with_capacity(count as usize);
306
307 for _ in 0..count {
308 ids.push(reader.read_u32_le()?);
309 }
310
311 Ok(BoneFileIds { ids })
312 }
313
314 pub fn len(&self) -> usize {
316 self.ids.len()
317 }
318
319 pub fn is_empty(&self) -> bool {
321 self.ids.is_empty()
322 }
323
324 pub fn get(&self, index: usize) -> Option<u32> {
326 self.ids.get(index).copied()
327 }
328
329 pub fn iter(&self) -> std::slice::Iter<'_, u32> {
331 self.ids.iter()
332 }
333}
334
335impl LodData {
336 pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
338 const LOD_LEVEL_SIZE: u32 = 14;
344
345 if !reader.chunk_size().is_multiple_of(LOD_LEVEL_SIZE) {
346 return Err(crate::error::M2Error::ParseError(format!(
347 "LDV1 chunk size {} is not a multiple of LOD level size {}",
348 reader.chunk_size(),
349 LOD_LEVEL_SIZE
350 )));
351 }
352
353 let count = reader.chunk_size() / LOD_LEVEL_SIZE;
354 let mut levels = Vec::with_capacity(count as usize);
355
356 for _ in 0..count {
357 let distance = reader.read_f32_le()?;
358 let skin_file_index = reader.read_u16_le()?;
359 let vertex_count = reader.read_u32_le()?;
360 let triangle_count = reader.read_u32_le()?;
361
362 levels.push(LodLevel {
363 distance,
364 skin_file_index,
365 vertex_count,
366 triangle_count,
367 });
368 }
369
370 Ok(LodData { levels })
371 }
372
373 pub fn select_lod(&self, distance: f32) -> Option<&LodLevel> {
375 self.levels
378 .iter()
379 .find(|lod| distance <= lod.distance)
380 .or_else(|| {
381 self.levels.last()
383 })
384 }
385
386 pub fn len(&self) -> usize {
388 self.levels.len()
389 }
390
391 pub fn is_empty(&self) -> bool {
393 self.levels.is_empty()
394 }
395}
396
397impl PhysicsData {
398 pub fn parse(data: &[u8]) -> Result<Self> {
401 use std::io::Cursor;
402
403 if data.len() < 8 {
404 return Err(crate::error::M2Error::ParseError(
405 "PHYS file too small for header".to_string(),
406 ));
407 }
408
409 let mut cursor = Cursor::new(data);
410
411 let mut magic = [0u8; 4];
413 cursor.read_exact(&mut magic).map_err(|e| {
414 crate::error::M2Error::ParseError(format!("Failed to read PHYS magic: {}", e))
415 })?;
416
417 if &magic != b"PHYS" {
418 return Err(crate::error::M2Error::ParseError(format!(
419 "Invalid PHYS magic: {:?}, expected PHYS",
420 magic
421 )));
422 }
423
424 let _chunk_size = cursor.read_u32_le().map_err(|e| {
425 crate::error::M2Error::ParseError(format!("Failed to read PHYS chunk size: {}", e))
426 })?;
427
428 let mut collision_mesh = None;
433 let mut material_properties = Vec::new();
434
435 if cursor.position() < data.len() as u64 {
437 collision_mesh = Some(CollisionMesh {
440 vertices: Vec::new(),
441 triangles: Vec::new(),
442 });
443
444 material_properties.push(PhysicsMaterial {
446 material_id: 0,
447 friction: 0.5,
448 restitution: 0.0,
449 density: 1.0,
450 });
451 }
452
453 Ok(PhysicsData {
454 collision_mesh,
455 material_properties,
456 })
457 }
458}
459
460impl SkeletonData {
461 pub fn parse(data: &[u8]) -> Result<Self> {
464 use std::io::Cursor;
465
466 if data.len() < 8 {
467 return Err(crate::error::M2Error::ParseError(
468 "SKEL file too small for header".to_string(),
469 ));
470 }
471
472 let mut cursor = Cursor::new(data);
473 let mut bone_hierarchy = Vec::new();
474 let mut root_bones = Vec::new();
475
476 while (cursor.position() as usize) < data.len() {
478 if data.len() - (cursor.position() as usize) < 8 {
479 break; }
481
482 let mut magic = [0u8; 4];
483 cursor.read_exact(&mut magic).map_err(|e| {
484 crate::error::M2Error::ParseError(format!("Failed to read chunk magic: {}", e))
485 })?;
486
487 let chunk_size = cursor.read_u32_le().map_err(|e| {
488 crate::error::M2Error::ParseError(format!("Failed to read chunk size: {}", e))
489 })?;
490
491 match &magic {
492 b"SKB1" => {
493 if chunk_size >= 16 {
498 let bone_count = std::cmp::min(chunk_size / 16, 64) as usize; for i in 0..bone_count {
503 let bone_node = BoneNode {
504 bone_index: i as u16,
505 parent_index: if i == 0 { -1 } else { (i - 1) as i16 },
506 children: Vec::new(),
507 local_transform: [
508 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
509 0.0, 0.0, 0.0, 1.0,
510 ], };
512 bone_hierarchy.push(bone_node);
513 }
514
515 if !bone_hierarchy.is_empty() {
516 root_bones.push(0); }
518 }
519 }
520 _ => {
521 cursor.set_position(cursor.position() + chunk_size as u64);
523 }
524 }
525 }
526
527 Ok(SkeletonData {
528 bone_hierarchy,
529 root_bones,
530 })
531 }
532}
533
534impl BoneData {
535 pub fn parse(data: &[u8]) -> Result<Self> {
538 use std::io::Cursor;
539
540 if data.len() < 12 {
541 return Err(crate::error::M2Error::ParseError(
543 "BONE file too small".to_string(),
544 ));
545 }
546
547 let mut cursor = Cursor::new(data);
548
549 let version = cursor.read_u32_le().map_err(|e| {
551 crate::error::M2Error::ParseError(format!("Failed to read .bone version: {}", e))
552 })?;
553
554 if version != 1 {
556 return Err(crate::error::M2Error::ParseError(format!(
557 "Unsupported .bone version: {}, expected 1",
558 version
559 )));
560 }
561
562 let bone_count = cursor.read_u32_le().map_err(|e| {
564 crate::error::M2Error::ParseError(format!("Failed to read bone count: {}", e))
565 })?;
566
567 if bone_count == 0 || bone_count > 1024 {
568 return Err(crate::error::M2Error::ParseError(format!(
570 "Invalid bone count: {}",
571 bone_count
572 )));
573 }
574
575 let bone_index = cursor.read_u16_le().map_err(|e| {
577 crate::error::M2Error::ParseError(format!("Failed to read bone ID: {}", e))
578 })?;
579
580 cursor.set_position(cursor.position() + (bone_count as u64 - 1) * 2);
582
583 cursor.set_position(cursor.position() + bone_count as u64 * 64);
586
587 let translation_track = AnimationTrack {
590 timestamps: vec![0, 1000], values: vec![[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], interpolation: InterpolationType::Linear,
593 };
594
595 let rotation_track = AnimationTrack {
596 timestamps: vec![0, 1000],
597 values: vec![[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0]], interpolation: InterpolationType::Linear,
599 };
600
601 let scale_track = AnimationTrack {
602 timestamps: vec![0, 1000],
603 values: vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], interpolation: InterpolationType::Linear,
605 };
606
607 Ok(BoneData {
608 bone_index,
609 translation_track,
610 rotation_track,
611 scale_track,
612 })
613 }
614}
615
616impl<T> AnimationTrack<T> {
617 pub fn new(interpolation: InterpolationType) -> Self {
619 AnimationTrack {
620 timestamps: Vec::new(),
621 values: Vec::new(),
622 interpolation,
623 }
624 }
625
626 pub fn len(&self) -> usize {
628 self.timestamps.len()
629 }
630
631 pub fn is_empty(&self) -> bool {
633 self.timestamps.is_empty()
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640 use crate::chunks::infrastructure::ChunkHeader;
641 use std::io::Cursor;
642
643 #[test]
644 fn test_sfid_parsing() {
645 let data = vec![
647 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, ];
651
652 let header = ChunkHeader {
653 magic: *b"SFID",
654 size: 12,
655 };
656 let cursor = Cursor::new(data);
657 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
658
659 let sfid = SkinFileIds::read(&mut chunk_reader).unwrap();
660
661 assert_eq!(sfid.len(), 3);
662 assert_eq!(sfid.get(0), Some(1));
663 assert_eq!(sfid.get(1), Some(2));
664 assert_eq!(sfid.get(2), Some(3));
665 assert_eq!(sfid.get(3), None);
666
667 let ids: Vec<u32> = sfid.iter().copied().collect();
668 assert_eq!(ids, vec![1, 2, 3]);
669 }
670
671 #[test]
672 fn test_afid_parsing() {
673 let data = vec![
674 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, ];
677
678 let header = ChunkHeader {
679 magic: *b"AFID",
680 size: 8,
681 };
682 let cursor = Cursor::new(data);
683 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
684
685 let afid = AnimationFileIds::read(&mut chunk_reader).unwrap();
686
687 assert_eq!(afid.len(), 2);
688 assert_eq!(afid.get(0), Some(16));
689 assert_eq!(afid.get(1), Some(32));
690 assert!(!afid.is_empty());
691 }
692
693 #[test]
694 fn test_txid_parsing() {
695 let data = vec![
696 0xFF, 0x00, 0x01, 0x00, ];
698
699 let header = ChunkHeader {
700 magic: *b"TXID",
701 size: 4,
702 };
703 let cursor = Cursor::new(data);
704 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
705
706 let txid = TextureFileIds::read(&mut chunk_reader).unwrap();
707
708 assert_eq!(txid.len(), 1);
709 assert_eq!(txid.get(0), Some(65791));
710 assert!(!txid.is_empty());
711 }
712
713 #[test]
714 fn test_pfid_parsing() {
715 let data = vec![
716 0x40, 0x00, 0x05, 0x00, ];
718
719 let header = ChunkHeader {
720 magic: *b"PFID",
721 size: 4,
722 };
723 let cursor = Cursor::new(data);
724 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
725
726 let pfid = PhysicsFileId::read(&mut chunk_reader).unwrap();
727
728 assert_eq!(pfid.id, 327744);
729 }
730
731 #[test]
732 fn test_skid_parsing() {
733 let data = vec![
734 0x80, 0x00, 0x02, 0x00, ];
736
737 let header = ChunkHeader {
738 magic: *b"SKID",
739 size: 4,
740 };
741 let cursor = Cursor::new(data);
742 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
743
744 let skid = SkeletonFileId::read(&mut chunk_reader).unwrap();
745
746 assert_eq!(skid.id, 131200);
747 }
748
749 #[test]
750 fn test_bfid_parsing() {
751 let data = vec![
752 0x01, 0x00, 0x10, 0x00, 0x02, 0x00, 0x10, 0x00, 0x03, 0x00, 0x10, 0x00, ];
756
757 let header = ChunkHeader {
758 magic: *b"BFID",
759 size: 12,
760 };
761 let cursor = Cursor::new(data);
762 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
763
764 let bfid = BoneFileIds::read(&mut chunk_reader).unwrap();
765
766 assert_eq!(bfid.len(), 3);
767 assert_eq!(bfid.get(0), Some(1048577));
768 assert_eq!(bfid.get(1), Some(1048578));
769 assert_eq!(bfid.get(2), Some(1048579));
770 assert!(!bfid.is_empty());
771 }
772
773 #[test]
774 fn test_ldv1_parsing() {
775 let data = vec![
776 0x00, 0x00, 0xA0, 0x42, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x42, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, ];
787
788 let header = ChunkHeader {
789 magic: *b"LDV1",
790 size: 28,
791 };
792 let cursor = Cursor::new(data);
793 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
794
795 let ldv1 = LodData::read(&mut chunk_reader).unwrap();
796
797 assert_eq!(ldv1.len(), 2);
798 assert!(!ldv1.is_empty());
799
800 let level0 = &ldv1.levels[0];
801 assert_eq!(level0.distance, 80.0);
802 assert_eq!(level0.skin_file_index, 0);
803 assert_eq!(level0.vertex_count, 4096);
804 assert_eq!(level0.triangle_count, 8192);
805
806 let level1 = &ldv1.levels[1];
807 assert_eq!(level1.distance, 100.0);
808 assert_eq!(level1.skin_file_index, 1);
809 assert_eq!(level1.vertex_count, 2048);
810 assert_eq!(level1.triangle_count, 4096);
811
812 assert_eq!(ldv1.select_lod(50.0).unwrap().skin_file_index, 0); assert_eq!(ldv1.select_lod(90.0).unwrap().skin_file_index, 1); assert_eq!(ldv1.select_lod(200.0).unwrap().skin_file_index, 1); }
817
818 #[test]
819 fn test_pfid_invalid_size() {
820 let data = vec![0x01, 0x02]; let header = ChunkHeader {
823 magic: *b"PFID",
824 size: 2,
825 };
826 let cursor = Cursor::new(data);
827 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
828
829 let result = PhysicsFileId::read(&mut chunk_reader);
830 assert!(result.is_err());
831 }
832
833 #[test]
834 fn test_ldv1_invalid_size() {
835 let data = vec![0x01, 0x02, 0x03]; let header = ChunkHeader {
838 magic: *b"LDV1",
839 size: 3,
840 };
841 let cursor = Cursor::new(data);
842 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
843
844 let result = LodData::read(&mut chunk_reader);
845 assert!(result.is_err());
846 }
847
848 #[test]
849 fn test_empty_chunks() {
850 let header = ChunkHeader {
852 magic: *b"SFID",
853 size: 0,
854 };
855 let cursor = Cursor::new(Vec::new());
856 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
857
858 let sfid = SkinFileIds::read(&mut chunk_reader).unwrap();
859 assert!(sfid.is_empty());
860 assert_eq!(sfid.len(), 0);
861 }
862
863 #[test]
864 fn test_physics_data_parsing_valid() {
865 let data = vec![
867 b'P', b'H', b'Y', b'S', 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, ];
871
872 let result = PhysicsData::parse(&data).unwrap();
873 assert!(result.collision_mesh.is_some());
874 assert_eq!(result.material_properties.len(), 1);
875 assert_eq!(result.material_properties[0].material_id, 0);
876 assert_eq!(result.material_properties[0].friction, 0.5);
877 }
878
879 #[test]
880 fn test_physics_data_parsing_invalid_magic() {
881 let data = vec![
882 b'B', b'A', b'D', b'!', 0x04, 0x00, 0x00, 0x00, ];
885
886 let result = PhysicsData::parse(&data);
887 assert!(result.is_err());
888 assert!(
889 result
890 .unwrap_err()
891 .to_string()
892 .contains("Invalid PHYS magic")
893 );
894 }
895
896 #[test]
897 fn test_physics_data_parsing_too_small() {
898 let data = vec![b'P', b'H', b'Y']; let result = PhysicsData::parse(&data);
901 assert!(result.is_err());
902 assert!(result.unwrap_err().to_string().contains("too small"));
903 }
904
905 #[test]
906 fn test_skeleton_data_parsing_valid() {
907 let data = vec![
909 b'S', b'K', b'B', b'1', 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, ];
916
917 let result = SkeletonData::parse(&data).unwrap();
918 assert!(!result.bone_hierarchy.is_empty());
919 assert!(!result.root_bones.is_empty());
920 assert_eq!(result.root_bones[0], 0); }
922
923 #[test]
924 fn test_skeleton_data_parsing_empty() {
925 let data = vec![
926 b'U', b'N', b'K', b'N', 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
930
931 let result = SkeletonData::parse(&data).unwrap();
932 assert!(result.bone_hierarchy.is_empty());
933 assert!(result.root_bones.is_empty());
934 }
935
936 #[test]
937 fn test_skeleton_data_parsing_too_small() {
938 let data = vec![b'S', b'K', b'B']; let result = SkeletonData::parse(&data);
941 assert!(result.is_err());
942 assert!(result.unwrap_err().to_string().contains("too small"));
943 }
944
945 #[test]
946 fn test_bone_data_parsing_valid() {
947 let data = vec![
949 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
954 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00,
955 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
956 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
957 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F,
958 ];
959
960 let result = BoneData::parse(&data).unwrap();
961 assert_eq!(result.bone_index, 5);
962 assert_eq!(result.translation_track.timestamps.len(), 2);
963 assert_eq!(result.rotation_track.timestamps.len(), 2);
964 assert_eq!(result.scale_track.timestamps.len(), 2);
965 assert_eq!(
966 result.translation_track.interpolation,
967 InterpolationType::Linear
968 );
969 }
970
971 #[test]
972 fn test_bone_data_parsing_invalid_version() {
973 let mut data = vec![
975 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, ];
979 data.extend(vec![0u8; 64]);
981
982 let result = BoneData::parse(&data);
983 assert!(result.is_err());
984 let error_msg = result.unwrap_err().to_string();
985 assert!(error_msg.contains("Unsupported .bone version"));
986 }
987
988 #[test]
989 fn test_bone_data_parsing_invalid_bone_count() {
990 let mut data = vec![
992 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
995 data.extend(vec![0u8; 64]);
997
998 let result = BoneData::parse(&data);
999 assert!(result.is_err());
1000 let error_msg = result.unwrap_err().to_string();
1001 assert!(error_msg.contains("Invalid bone count"));
1002 }
1003
1004 #[test]
1005 fn test_bone_data_parsing_too_small() {
1006 let data = vec![0x01, 0x00, 0x00]; let result = BoneData::parse(&data);
1009 assert!(result.is_err());
1010 assert!(result.unwrap_err().to_string().contains("too small"));
1011 }
1012
1013 #[test]
1014 fn test_animation_track_properties() {
1015 let track = AnimationTrack::<[f32; 3]>::new(InterpolationType::Bezier);
1016 assert!(track.is_empty());
1017 assert_eq!(track.len(), 0);
1018 assert_eq!(track.interpolation, InterpolationType::Bezier);
1019
1020 let track_with_data = AnimationTrack {
1021 timestamps: vec![0, 500, 1000],
1022 values: vec![[0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [2.0, 2.0, 2.0]],
1023 interpolation: InterpolationType::Linear,
1024 };
1025 assert!(!track_with_data.is_empty());
1026 assert_eq!(track_with_data.len(), 3);
1027 }
1028
1029 #[test]
1030 fn test_interpolation_type_equality() {
1031 assert_eq!(InterpolationType::None, InterpolationType::None);
1032 assert_eq!(InterpolationType::Linear, InterpolationType::Linear);
1033 assert_eq!(InterpolationType::Bezier, InterpolationType::Bezier);
1034 assert_ne!(InterpolationType::None, InterpolationType::Linear);
1035 }
1036}