physis/
skeleton.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#![allow(unused)]
5#![allow(clippy::needless_late_init)]
6#![allow(clippy::upper_case_acronyms)]
7
8use binrw::helpers::until_eof;
9use binrw::{BinRead, binread};
10use std::io::{Cursor, SeekFrom};
11
12use crate::ByteSpan;
13use crate::havok::{HavokAnimationContainer, HavokBinaryTagFileReader};
14
15#[binread]
16#[br(little)]
17struct SklbV1 {
18    unk_offset: u16,
19    havok_offset: u16,
20    body_id: u32,
21    mapper_body_id1: u32,
22    mapper_body_id2: u32,
23    mapper_body_id3: u32,
24}
25
26#[binread]
27#[br(little)]
28struct SklbV2 {
29    unk_offset: u32,
30    havok_offset: u32,
31    unk: u32,
32    body_id: u32,
33    mapper_body_id1: u32,
34    mapper_body_id2: u32,
35    mapper_body_id3: u32,
36}
37
38#[binread]
39#[br(magic = 0x736B6C62i32)]
40#[br(little)]
41struct SKLB {
42    version: u32,
43
44    #[br(if(version == 0x3132_3030u32))]
45    sklb_v1: Option<SklbV1>,
46
47    #[br(if(version == 0x3133_3030u32 || version == 0x3133_3031u32))]
48    sklb_v2: Option<SklbV2>,
49
50    #[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 })))]
51    #[br(parse_with = until_eof)]
52    raw_data: Vec<u8>,
53}
54
55#[derive(Debug)]
56pub struct Bone {
57    /// Name of the bone
58    pub name: String,
59    /// Index of the parent bone in the Skeleton's `bones` vector
60    pub parent_index: i32,
61
62    /// Position of the bone
63    pub position: [f32; 3],
64    /// Rotation quanternion of the bone
65    pub rotation: [f32; 4],
66    /// Scale of the bone
67    pub scale: [f32; 3],
68}
69
70#[derive(Debug)]
71pub struct Skeleton {
72    /// Bones of this skeleton
73    pub bones: Vec<Bone>,
74}
75
76impl Skeleton {
77    /// Reads an existing SKLB file
78    pub fn from_existing(buffer: ByteSpan) -> Option<Skeleton> {
79        let mut cursor = Cursor::new(buffer);
80
81        let sklb = SKLB::read(&mut cursor).ok()?;
82
83        let root = HavokBinaryTagFileReader::read(&sklb.raw_data);
84        let raw_animation_container = root.find_object_by_type("hkaAnimationContainer");
85        let animation_container = HavokAnimationContainer::new(raw_animation_container);
86
87        let havok_skeleton = &animation_container.skeletons[0];
88
89        let mut skeleton = Skeleton { bones: vec![] };
90
91        for (index, bone) in havok_skeleton.bone_names.iter().enumerate() {
92            skeleton.bones.push(Bone {
93                name: bone.clone(),
94                parent_index: havok_skeleton.parent_indices[index] as i32,
95                position: [
96                    havok_skeleton.reference_pose[index].translation[0],
97                    havok_skeleton.reference_pose[index].translation[1],
98                    havok_skeleton.reference_pose[index].translation[2],
99                ],
100                rotation: havok_skeleton.reference_pose[index].rotation,
101                scale: [
102                    havok_skeleton.reference_pose[index].scale[0],
103                    havok_skeleton.reference_pose[index].scale[1],
104                    havok_skeleton.reference_pose[index].scale[2],
105                ],
106            });
107        }
108
109        Some(skeleton)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use std::fs::read;
116    use std::path::PathBuf;
117
118    use super::*;
119
120    #[test]
121    fn test_invalid() {
122        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
123        d.push("resources/tests");
124        d.push("random");
125
126        // Feeding it invalid data should not panic
127        Skeleton::from_existing(&read(d).unwrap());
128    }
129}