1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#![allow(unused)]
#![allow(clippy::needless_late_init)]
#![allow(clippy::upper_case_acronyms)]

use binrw::helpers::until_eof;
use binrw::{binread, BinRead};
use std::io::{Cursor, SeekFrom};

use crate::havok::{HavokAnimationContainer, HavokBinaryTagFileReader};
use crate::ByteSpan;

#[binread]
#[br(little)]
struct SklbV1 {
    unk_offset: u16,
    havok_offset: u16,
    body_id: u32,
    mapper_body_id1: u32,
    mapper_body_id2: u32,
    mapper_body_id3: u32,
}

#[binread]
#[br(little)]
struct SklbV2 {
    unk_offset: u32,
    havok_offset: u32,
    unk: u32,
    body_id: u32,
    mapper_body_id1: u32,
    mapper_body_id2: u32,
    mapper_body_id3: u32,
}

#[binread]
#[br(magic = 0x736B6C62i32)]
#[br(little)]
struct SKLB {
    version: u32,

    #[br(if(version == 0x3132_3030u32))]
    sklb_v1: Option<SklbV1>,

    #[br(if(version == 0x3133_3030u32 || version == 0x3133_3031u32))]
    sklb_v2: Option<SklbV2>,

    #[br(seek_before(SeekFrom::Start(if (version == 0x3132_3030u32) { sklb_v1.as_ref().unwrap().havok_offset as u64 } else { sklb_v2.as_ref().unwrap().havok_offset as u64 })))]
    #[br(parse_with = until_eof)]
    raw_data: Vec<u8>,
}

#[derive(Debug)]
pub struct Bone {
    /// Name of the bone
    pub name: String,
    /// Index of the parent bone in the Skeleton's `bones` vector
    pub parent_index: i32,

    /// Position of the bone
    pub position: [f32; 3],
    /// Rotation quanternion of the bone
    pub rotation: [f32; 4],
    /// Scale of the bone
    pub scale: [f32; 3],
}

#[derive(Debug)]
pub struct Skeleton {
    /// Bones of this skeleton
    pub bones: Vec<Bone>,
}

impl Skeleton {
    /// Reads an existing SKLB file
    pub fn from_existing(buffer: ByteSpan) -> Option<Skeleton> {
        let mut cursor = Cursor::new(buffer);

        let sklb = SKLB::read(&mut cursor).ok()?;

        let root = HavokBinaryTagFileReader::read(&sklb.raw_data);
        let raw_animation_container = root.find_object_by_type("hkaAnimationContainer");
        let animation_container = HavokAnimationContainer::new(raw_animation_container);

        let havok_skeleton = &animation_container.skeletons[0];

        let mut skeleton = Skeleton { bones: vec![] };

        for (index, bone) in havok_skeleton.bone_names.iter().enumerate() {
            skeleton.bones.push(Bone {
                name: bone.clone(),
                parent_index: havok_skeleton.parent_indices[index] as i32,
                position: [
                    havok_skeleton.reference_pose[index].translation[0],
                    havok_skeleton.reference_pose[index].translation[1],
                    havok_skeleton.reference_pose[index].translation[2],
                ],
                rotation: havok_skeleton.reference_pose[index].rotation,
                scale: [
                    havok_skeleton.reference_pose[index].scale[0],
                    havok_skeleton.reference_pose[index].scale[1],
                    havok_skeleton.reference_pose[index].scale[2],
                ],
            });
        }

        Some(skeleton)
    }
}

#[cfg(test)]
mod tests {
    use std::fs::read;
    use std::path::PathBuf;

    use super::*;

    #[test]
    fn test_invalid() {
        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        d.push("resources/tests");
        d.push("random");

        // Feeding it invalid data should not panic
        Skeleton::from_existing(&read(d).unwrap());
    }
}