wow_m2/chunks/
file_references.rs

1//! File reference chunks for M2 chunked format (MD21+)
2//!
3//! These chunks contain FileDataID references to external files that are
4//! loaded separately from the main M2 model.
5
6use std::io::{Read, Seek};
7
8use crate::chunks::infrastructure::ChunkReader;
9use crate::error::Result;
10use crate::io_ext::ReadExt;
11
12/// SFID chunk - Skin File References
13/// Contains an array of FileDataIDs for skin files (.skin files)
14#[derive(Debug, Clone)]
15pub struct SkinFileIds {
16    /// Array of FileDataIDs for skin files
17    pub ids: Vec<u32>,
18}
19
20impl SkinFileIds {
21    /// Read SFID chunk data from a chunk reader
22    pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
23        let count = reader.chunk_size() / 4; // Each ID is 4 bytes
24        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    /// Get the number of skin file IDs
34    pub fn len(&self) -> usize {
35        self.ids.len()
36    }
37
38    /// Check if there are no skin file IDs
39    pub fn is_empty(&self) -> bool {
40        self.ids.is_empty()
41    }
42
43    /// Get a skin file ID by index
44    pub fn get(&self, index: usize) -> Option<u32> {
45        self.ids.get(index).copied()
46    }
47
48    /// Iterate over all skin file IDs
49    pub fn iter(&self) -> std::slice::Iter<'_, u32> {
50        self.ids.iter()
51    }
52}
53
54/// AFID chunk - Animation File References
55/// Contains an array of FileDataIDs for external animation files (.anim files)
56#[derive(Debug, Clone)]
57pub struct AnimationFileIds {
58    /// Array of FileDataIDs for animation files
59    pub ids: Vec<u32>,
60}
61
62impl AnimationFileIds {
63    /// Read AFID chunk data from a chunk reader
64    pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
65        let count = reader.chunk_size() / 4; // Each ID is 4 bytes
66        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    /// Get the number of animation file IDs
76    pub fn len(&self) -> usize {
77        self.ids.len()
78    }
79
80    /// Check if there are no animation file IDs
81    pub fn is_empty(&self) -> bool {
82        self.ids.is_empty()
83    }
84
85    /// Get an animation file ID by index
86    pub fn get(&self, index: usize) -> Option<u32> {
87        self.ids.get(index).copied()
88    }
89
90    /// Iterate over all animation file IDs
91    pub fn iter(&self) -> std::slice::Iter<'_, u32> {
92        self.ids.iter()
93    }
94}
95
96/// TXID chunk - Texture File References
97/// Contains an array of FileDataIDs for texture files (.blp files)
98#[derive(Debug, Clone)]
99pub struct TextureFileIds {
100    /// Array of FileDataIDs for texture files
101    pub ids: Vec<u32>,
102}
103
104/// PFID chunk - Physics File Reference
105/// Contains a single FileDataID for the physics file (.phys file)
106#[derive(Debug, Clone)]
107pub struct PhysicsFileId {
108    /// FileDataID for the physics file
109    pub id: u32,
110}
111
112/// SKID chunk - Skeleton File Reference
113/// Contains a single FileDataID for the skeleton file (.skel file)
114#[derive(Debug, Clone)]
115pub struct SkeletonFileId {
116    /// FileDataID for the skeleton file
117    pub id: u32,
118}
119
120/// BFID chunk - Bone File References
121/// Contains an array of FileDataIDs for bone files (.bone files)
122#[derive(Debug, Clone)]
123pub struct BoneFileIds {
124    /// Array of FileDataIDs for bone files
125    pub ids: Vec<u32>,
126}
127
128/// LDV1 chunk - Level of Detail Configuration
129/// Contains level of detail settings and skin file assignments
130#[derive(Debug, Clone)]
131pub struct LodData {
132    /// LOD levels configuration
133    pub levels: Vec<LodLevel>,
134}
135
136/// Single LOD level configuration
137#[derive(Debug, Clone)]
138pub struct LodLevel {
139    /// Distance threshold for this LOD level
140    pub distance: f32,
141    /// Skin file index to use for this LOD level
142    pub skin_file_index: u16,
143    /// Number of vertices at this LOD level
144    pub vertex_count: u32,
145    /// Number of triangles at this LOD level
146    pub triangle_count: u32,
147}
148
149/// Basic physics data structure for .phys files
150#[derive(Debug, Clone)]
151pub struct PhysicsData {
152    /// Collision mesh data (if present)
153    pub collision_mesh: Option<CollisionMesh>,
154    /// Physics material properties
155    pub material_properties: Vec<PhysicsMaterial>,
156}
157
158/// Collision mesh for physics calculations
159#[derive(Debug, Clone)]
160pub struct CollisionMesh {
161    /// Collision vertices
162    pub vertices: Vec<[f32; 3]>,
163    /// Collision triangles (vertex indices)
164    pub triangles: Vec<[u16; 3]>,
165}
166
167/// Physics material properties
168#[derive(Debug, Clone)]
169pub struct PhysicsMaterial {
170    /// Material identifier
171    pub material_id: u32,
172    /// Friction coefficient
173    pub friction: f32,
174    /// Restitution (bounciness)
175    pub restitution: f32,
176    /// Density
177    pub density: f32,
178}
179
180/// Basic skeleton data structure for .skel files
181#[derive(Debug, Clone)]
182pub struct SkeletonData {
183    /// Bone hierarchy nodes
184    pub bone_hierarchy: Vec<BoneNode>,
185    /// Root bone indices
186    pub root_bones: Vec<u16>,
187}
188
189/// Bone node in skeleton hierarchy
190#[derive(Debug, Clone)]
191pub struct BoneNode {
192    /// Bone index in the model
193    pub bone_index: u16,
194    /// Parent bone index (-1 if root)
195    pub parent_index: i16,
196    /// Children bone indices
197    pub children: Vec<u16>,
198    /// Local transform relative to parent
199    pub local_transform: [f32; 16], // 4x4 matrix
200}
201
202/// Basic bone data structure for .bone files
203#[derive(Debug, Clone)]
204pub struct BoneData {
205    /// Bone index this data applies to
206    pub bone_index: u16,
207    /// Translation animation track
208    pub translation_track: AnimationTrack<[f32; 3]>,
209    /// Rotation animation track (quaternion)
210    pub rotation_track: AnimationTrack<[f32; 4]>,
211    /// Scale animation track
212    pub scale_track: AnimationTrack<[f32; 3]>,
213}
214
215/// Animation track for bone data
216#[derive(Debug, Clone)]
217pub struct AnimationTrack<T> {
218    /// Keyframe timestamps
219    pub timestamps: Vec<u32>,
220    /// Keyframe values
221    pub values: Vec<T>,
222    /// Interpolation type
223    pub interpolation: InterpolationType,
224}
225
226/// Animation interpolation types
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum InterpolationType {
229    /// No interpolation (step)
230    None,
231    /// Linear interpolation
232    Linear,
233    /// Bezier curve interpolation
234    Bezier,
235}
236
237impl TextureFileIds {
238    /// Read TXID chunk data from a chunk reader
239    pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
240        let count = reader.chunk_size() / 4; // Each ID is 4 bytes
241        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    /// Get the number of texture file IDs
251    pub fn len(&self) -> usize {
252        self.ids.len()
253    }
254
255    /// Check if there are no texture file IDs
256    pub fn is_empty(&self) -> bool {
257        self.ids.is_empty()
258    }
259
260    /// Get a texture file ID by index
261    pub fn get(&self, index: usize) -> Option<u32> {
262        self.ids.get(index).copied()
263    }
264
265    /// Iterate over all texture file IDs
266    pub fn iter(&self) -> std::slice::Iter<'_, u32> {
267        self.ids.iter()
268    }
269}
270
271impl PhysicsFileId {
272    /// Read PFID chunk data from a chunk reader
273    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    /// Read SKID chunk data from a chunk reader
288    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    /// Read BFID chunk data from a chunk reader
303    pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
304        let count = reader.chunk_size() / 4; // Each ID is 4 bytes
305        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    /// Get the number of bone file IDs
315    pub fn len(&self) -> usize {
316        self.ids.len()
317    }
318
319    /// Check if there are no bone file IDs
320    pub fn is_empty(&self) -> bool {
321        self.ids.is_empty()
322    }
323
324    /// Get a bone file ID by index
325    pub fn get(&self, index: usize) -> Option<u32> {
326        self.ids.get(index).copied()
327    }
328
329    /// Iterate over all bone file IDs
330    pub fn iter(&self) -> std::slice::Iter<'_, u32> {
331        self.ids.iter()
332    }
333}
334
335impl LodData {
336    /// Read LDV1 chunk data from a chunk reader
337    pub fn read<R: Read + Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
338        // LDV1 format: each LOD level is 14 bytes
339        // 4 bytes: distance (float)
340        // 2 bytes: skin_file_index (u16)
341        // 4 bytes: vertex_count (u32)
342        // 4 bytes: triangle_count (u32)
343        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    /// Select the appropriate LOD level for a given distance
374    pub fn select_lod(&self, distance: f32) -> Option<&LodLevel> {
375        // Find the first LOD level where the distance is less than or equal to the threshold
376        // LOD levels should be sorted by distance (closest first)
377        self.levels
378            .iter()
379            .find(|lod| distance <= lod.distance)
380            .or_else(|| {
381                // If no level matches, use the last (most distant) level
382                self.levels.last()
383            })
384    }
385
386    /// Get the number of LOD levels
387    pub fn len(&self) -> usize {
388        self.levels.len()
389    }
390
391    /// Check if there are no LOD levels
392    pub fn is_empty(&self) -> bool {
393        self.levels.is_empty()
394    }
395}
396
397impl PhysicsData {
398    /// Parse physics data from raw .phys file contents
399    /// .phys files are chunked format with PHYS header chunk followed by data chunks
400    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        // Read main PHYS chunk header
412        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        // Basic physics data - for now, parse minimal structure
429        // TODO: Implement full chunked parsing for BODY, SHAP, JOIN chunks
430
431        // Try to read basic collision data if present
432        let mut collision_mesh = None;
433        let mut material_properties = Vec::new();
434
435        // If there's data after the header, try to parse basic collision info
436        if cursor.position() < data.len() as u64 {
437            // For now, create a basic collision mesh placeholder
438            // Real implementation would parse BODY, SHAP chunks
439            collision_mesh = Some(CollisionMesh {
440                vertices: Vec::new(),
441                triangles: Vec::new(),
442            });
443
444            // Add default physics material
445            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    /// Parse skeleton data from raw .skel file contents
462    /// .skel files contain SKB1 chunk with bone hierarchy information
463    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        // Parse chunks until end of file
477        while (cursor.position() as usize) < data.len() {
478            if data.len() - (cursor.position() as usize) < 8 {
479                break; // Not enough data for chunk header
480            }
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                    // SKB1 contains skeleton bone data
494                    // For now, create basic bone hierarchy
495                    // TODO: Implement full SKB1 parsing with M2Array<M2CompBone>
496
497                    if chunk_size >= 16 {
498                        // Minimum size for one bone entry
499                        // Parse basic bone data - simplified for now
500                        let bone_count = std::cmp::min(chunk_size / 16, 64) as usize; // Limit bones
501
502                        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                                ], // Identity matrix
511                            };
512                            bone_hierarchy.push(bone_node);
513                        }
514
515                        if !bone_hierarchy.is_empty() {
516                            root_bones.push(0); // First bone is root
517                        }
518                    }
519                }
520                _ => {
521                    // Skip unknown chunks
522                    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    /// Parse bone data from raw .bone file contents
536    /// .bone files contain bone animation tracks with keyframes
537    pub fn parse(data: &[u8]) -> Result<Self> {
538        use std::io::Cursor;
539
540        if data.len() < 12 {
541            // Minimum: header(4) + bone_count(4) + bone_id(2) + matrix(64)
542            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        // Read .bone file header
550        let version = cursor.read_u32_le().map_err(|e| {
551            crate::error::M2Error::ParseError(format!("Failed to read .bone version: {}", e))
552        })?;
553
554        // Version should be 1 according to wowdev.wiki
555        if version != 1 {
556            return Err(crate::error::M2Error::ParseError(format!(
557                "Unsupported .bone version: {}, expected 1",
558                version
559            )));
560        }
561
562        // Read bone ID array count
563        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            // Reasonable limit
569            return Err(crate::error::M2Error::ParseError(format!(
570                "Invalid bone count: {}",
571                bone_count
572            )));
573        }
574
575        // Read first bone ID (we'll process just the first bone for now)
576        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        // Skip remaining bone IDs for now
581        cursor.set_position(cursor.position() + (bone_count as u64 - 1) * 2);
582
583        // Read bone offset matrices (skip for now as they're complex)
584        // Each matrix is 16 floats (64 bytes)
585        cursor.set_position(cursor.position() + bone_count as u64 * 64);
586
587        // Create basic animation tracks
588        // TODO: Parse actual keyframe data from the file
589        let translation_track = AnimationTrack {
590            timestamps: vec![0, 1000],                      // Basic 1-second animation
591            values: vec![[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], // No translation
592            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]], // Identity quaternion
598            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]], // Unity scale
604            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    /// Create a new empty animation track
618    pub fn new(interpolation: InterpolationType) -> Self {
619        AnimationTrack {
620            timestamps: Vec::new(),
621            values: Vec::new(),
622            interpolation,
623        }
624    }
625
626    /// Get the number of keyframes
627    pub fn len(&self) -> usize {
628        self.timestamps.len()
629    }
630
631    /// Check if the track has no keyframes
632    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        // Create test data: 3 FileDataIDs (12 bytes total)
646        let data = vec![
647            0x01, 0x00, 0x00, 0x00, // FileDataID 1
648            0x02, 0x00, 0x00, 0x00, // FileDataID 2
649            0x03, 0x00, 0x00, 0x00, // FileDataID 3
650        ];
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, // FileDataID 16
675            0x20, 0x00, 0x00, 0x00, // FileDataID 32
676        ];
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, // FileDataID 65791
697        ];
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, // FileDataID 327744
717        ];
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, // FileDataID 131200
735        ];
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, // FileDataID 1048577
753            0x02, 0x00, 0x10, 0x00, // FileDataID 1048578
754            0x03, 0x00, 0x10, 0x00, // FileDataID 1048579
755        ];
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            // First LOD level (14 bytes)
777            0x00, 0x00, 0xA0, 0x42, // distance = 80.0f
778            0x00, 0x00, // skin_file_index = 0
779            0x00, 0x10, 0x00, 0x00, // vertex_count = 4096
780            0x00, 0x20, 0x00, 0x00, // triangle_count = 8192
781            // Second LOD level (14 bytes)
782            0x00, 0x00, 0xC8, 0x42, // distance = 100.0f
783            0x01, 0x00, // skin_file_index = 1
784            0x00, 0x08, 0x00, 0x00, // vertex_count = 2048
785            0x00, 0x10, 0x00, 0x00, // triangle_count = 4096
786        ];
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        // Test LOD selection
813        assert_eq!(ldv1.select_lod(50.0).unwrap().skin_file_index, 0); // Close distance -> high detail
814        assert_eq!(ldv1.select_lod(90.0).unwrap().skin_file_index, 1); // Far distance -> low detail
815        assert_eq!(ldv1.select_lod(200.0).unwrap().skin_file_index, 1); // Very far -> use last level
816    }
817
818    #[test]
819    fn test_pfid_invalid_size() {
820        let data = vec![0x01, 0x02]; // Only 2 bytes instead of 4
821
822        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]; // Not a multiple of 14 bytes
836
837        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        // Test empty chunks
851        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        // Create valid PHYS chunk data
866        let data = vec![
867            b'P', b'H', b'Y', b'S', // PHYS magic
868            0x04, 0x00, 0x00, 0x00, // Chunk size: 4 bytes
869            0x01, 0x00, 0x00, 0x00, // Some physics data
870        ];
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'!', // Invalid magic
883            0x04, 0x00, 0x00, 0x00, // Chunk size
884        ];
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']; // Too small for header
899
900        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        // Create valid SKB1 chunk data
908        let data = vec![
909            b'S', b'K', b'B', b'1', // SKB1 magic
910            0x10, 0x00, 0x00, 0x00, // Chunk size: 16 bytes
911            0x01, 0x00, 0x00, 0x00, // Bone data (simplified)
912            0x02, 0x00, 0x00, 0x00, // More bone data
913            0x03, 0x00, 0x00, 0x00, // More bone data
914            0x04, 0x00, 0x00, 0x00, // More bone data
915        ];
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); // First bone is root
921    }
922
923    #[test]
924    fn test_skeleton_data_parsing_empty() {
925        let data = vec![
926            b'U', b'N', b'K', b'N', // Unknown chunk
927            0x04, 0x00, 0x00, 0x00, // Chunk size
928            0x00, 0x00, 0x00, 0x00, // Data
929        ];
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']; // Too small
939
940        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        // Create valid .bone file data
948        let data = vec![
949            0x01, 0x00, 0x00, 0x00, // Version: 1
950            0x01, 0x00, 0x00, 0x00, // Bone count: 1
951            0x05, 0x00, // Bone ID: 5
952            // 64 bytes for bone offset matrix (identity matrix as floats)
953            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        // Create data large enough to pass size check but with invalid version
974        let mut data = vec![
975            0x02, 0x00, 0x00, 0x00, // Version: 2 (unsupported)
976            0x01, 0x00, 0x00, 0x00, // Bone count: 1
977            0x05, 0x00, // Bone ID: 5
978        ];
979        // Add 64 bytes for bone offset matrix
980        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        // Create data large enough to pass size check but with invalid bone count
991        let mut data = vec![
992            0x01, 0x00, 0x00, 0x00, // Version: 1
993            0x00, 0x00, 0x00, 0x00, // Bone count: 0 (invalid)
994        ];
995        // Add extra bytes to meet minimum size requirement
996        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]; // Too small
1007
1008        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}