2023-08-06 08:25:04 -04:00
|
|
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2023-09-22 19:17:24 -04:00
|
|
|
#![allow(unused)]
|
|
|
|
#![allow(clippy::needless_late_init)]
|
|
|
|
#![allow(clippy::upper_case_acronyms)]
|
|
|
|
|
2023-10-13 14:55:27 -04:00
|
|
|
use std::io::{Cursor, SeekFrom};
|
|
|
|
use binrw::{binread, BinRead};
|
2023-09-22 19:17:24 -04:00
|
|
|
use binrw::helpers::until_eof;
|
2022-08-06 18:15:33 -04:00
|
|
|
|
2023-10-13 14:55:27 -04:00
|
|
|
use crate::havok::{HavokAnimationContainer, HavokBinaryTagFileReader};
|
2023-10-13 16:16:04 -04:00
|
|
|
use crate::ByteSpan;
|
2023-08-06 08:25:04 -04:00
|
|
|
|
2023-08-02 16:28:08 -04:00
|
|
|
#[binread]
|
2023-10-13 14:55:27 -04:00
|
|
|
#[br(little)]
|
2023-09-22 19:17:24 -04:00
|
|
|
struct SklbV1 {
|
2023-10-13 14:55:27 -04:00
|
|
|
unk_offset: u16,
|
|
|
|
havok_offset: u16,
|
|
|
|
body_id: u32,
|
|
|
|
mapper_body_id1: u32,
|
|
|
|
mapper_body_id2: u32,
|
|
|
|
mapper_body_id3: u32,
|
2023-08-02 16:28:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binread]
|
2023-10-13 14:55:27 -04:00
|
|
|
#[br(little)]
|
2023-09-22 19:17:24 -04:00
|
|
|
struct SklbV2 {
|
2023-10-13 14:55:27 -04:00
|
|
|
unk_offset: u32,
|
|
|
|
havok_offset: u32,
|
|
|
|
unk: u32,
|
|
|
|
body_id: u32,
|
|
|
|
mapper_body_id1: u32,
|
|
|
|
mapper_body_id2: u32,
|
|
|
|
mapper_body_id3: u32
|
2023-08-02 16:28:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binread]
|
|
|
|
#[br(magic = 0x736B6C62i32)]
|
2023-10-13 14:55:27 -04:00
|
|
|
#[br(little)]
|
2023-08-02 16:28:08 -04:00
|
|
|
struct SKLB {
|
2023-10-13 14:55:27 -04:00
|
|
|
version: u32,
|
2023-08-02 16:28:08 -04:00
|
|
|
|
2023-10-13 14:55:27 -04:00
|
|
|
#[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 })))]
|
2023-08-02 16:28:08 -04:00
|
|
|
#[br(parse_with = until_eof)]
|
|
|
|
raw_data: Vec<u8>
|
|
|
|
}
|
|
|
|
|
2022-08-06 18:15:33 -04:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Bone {
|
2023-12-02 19:57:04 -05:00
|
|
|
/// Name of the bone
|
2022-08-10 14:52:11 -04:00
|
|
|
pub name: String,
|
2023-12-02 19:57:04 -05:00
|
|
|
/// Index of the parent bone in the Skeleton's `bones` vector
|
2022-08-10 14:52:11 -04:00
|
|
|
pub parent_index: i32,
|
2022-08-06 18:15:33 -04:00
|
|
|
|
2023-12-02 19:57:04 -05:00
|
|
|
/// Position of the bone
|
2022-08-10 14:52:11 -04:00
|
|
|
pub position: [f32; 3],
|
2023-12-02 19:57:04 -05:00
|
|
|
/// Rotation quanternion of the bone
|
2022-08-10 14:52:11 -04:00
|
|
|
pub rotation: [f32; 4],
|
2023-12-02 19:57:04 -05:00
|
|
|
/// Scale of the bone
|
2022-08-16 11:52:07 -04:00
|
|
|
pub scale: [f32; 3],
|
2022-08-06 18:15:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Skeleton {
|
2023-12-02 19:57:04 -05:00
|
|
|
/// Bones of this skeleton
|
2022-08-16 11:52:07 -04:00
|
|
|
pub bones: Vec<Bone>,
|
2022-08-06 18:15:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Skeleton {
|
2023-12-02 19:57:04 -05:00
|
|
|
/// Reads an existing SKLB file
|
2023-10-13 16:16:04 -04:00
|
|
|
pub fn from_existing(buffer: ByteSpan) -> Option<Skeleton> {
|
2023-10-13 14:55:27 -04:00
|
|
|
let mut cursor = Cursor::new(buffer);
|
2022-08-06 18:15:33 -04:00
|
|
|
|
2024-04-16 22:07:30 -04:00
|
|
|
let sklb = SKLB::read(&mut cursor).ok()?;
|
2022-08-06 18:15:33 -04:00
|
|
|
|
2023-10-13 14:55:27 -04:00
|
|
|
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);
|
2022-08-06 18:15:33 -04:00
|
|
|
|
2023-10-13 14:55:27 -04:00
|
|
|
let havok_skeleton = &animation_container.skeletons[0];
|
2022-08-06 18:15:33 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
let mut skeleton = Skeleton { bones: vec![] };
|
2022-08-06 18:15:33 -04:00
|
|
|
|
2023-10-13 14:55:27 -04:00
|
|
|
for (index, bone) in havok_skeleton.bone_names.iter().enumerate() {
|
2022-08-06 18:15:33 -04:00
|
|
|
skeleton.bones.push(Bone {
|
2023-10-13 14:55:27 -04:00
|
|
|
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]],
|
2022-08-06 18:15:33 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(skeleton)
|
|
|
|
}
|
2022-08-16 11:52:07 -04:00
|
|
|
}
|
2024-04-16 22:07:30 -04:00
|
|
|
|
|
|
|
#[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());
|
|
|
|
}
|
|
|
|
}
|