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
//! The [Skel] format stores the model's skeleton used for skeletal animations.
//! These files typically use the ".nusktb" suffix like "model.nusktb".
//! Animations are often stored in [Anim](crate::formats::anim::Anim) files that override the [Skel] file's bone transforms.
//! [Skel] files are linked with [Mesh](crate::formats::mesh::Mesh) and [Matl](crate::formats::matl::Matl) files using a [Modl](crate::formats::modl::Modl) file.
use crate::{Matrix4x4, SsbhArray, SsbhString, Version};
use binrw::BinRead;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "strum")]
use strum::{Display, EnumIter, EnumString, FromRepr};
use ssbh_write::SsbhWrite;
/// An ordered, hierarchical collection of bones and their associated transforms.
/// Each bone entry has transformation matrices stored at the corresponding locations in the transform arrays.
/// The [transforms](#structfield.transforms) array can be used to calculate the remaining arrays.
/// Compatible with file version 1.0.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinRead, SsbhWrite, PartialEq, Clone)]
#[br(import(major_version: u16, minor_version: u16))]
pub enum Skel {
#[br(pre_assert(major_version == 1 && minor_version == 0))]
V10 {
/// A skeleton consisting of an ordered hierarchy of bones.
bone_entries: SsbhArray<SkelBoneEntry>,
/// The transformation in world space for each bone in
/// [bone_entries](#structfield.bone_entries).
/// The world space transform for a bone is calculated by accumulating the transformations in [transforms](#structfield.transforms)
/// with the transformation of the bone's parent recursively.
world_transforms: SsbhArray<Matrix4x4>,
/// The inverses of the matrices in [world_transforms](#structfield.world_transforms).
inv_world_transforms: SsbhArray<Matrix4x4>,
/// The associated transformation for each of the bones in [bone_entries](#structfield.bone_entries) relative to its parent's world transform.
/// If the bone has no parent, this is equivalent to the corresponding value in [world_transforms](#structfield.world_transforms).
transforms: SsbhArray<Matrix4x4>,
/// The inverses of the matrices in [transforms](#structfield.transforms).
inv_transforms: SsbhArray<Matrix4x4>,
},
}
impl Version for Skel {
fn major_minor_version(&self) -> (u16, u16) {
match self {
Skel::V10 { .. } => (1, 0),
}
}
}
/// A named bone.
/// [index](#structfield.index) and [parent_index](#structfield.parent_index) determine the skeleton's bone hierarchy.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinRead, SsbhWrite, Clone, PartialEq, Eq)]
pub struct SkelBoneEntry {
/// The name of the bone.
pub name: SsbhString,
/// The index of this [SkelBoneEntry] in [bone_entries](struct.Skel.html.#structfield.bone_entries).
pub index: u16,
/// The index of the parent [SkelBoneEntry] in [bone_entries](struct.Skel.html.#structfield.bone_entries) or `-1` if there is no parent.
pub parent_index: i16,
pub flags: SkelEntryFlags,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinRead, SsbhWrite, Clone, PartialEq, Eq)]
#[ssbhwrite(pad_after = 2)]
pub struct SkelEntryFlags {
pub unk1: u8, // TODO: usually 1?
#[br(pad_after = 2)]
pub billboard_type: BillboardType,
}
/// Billboarding reorients a [MeshObject](crate::formats::mesh::MeshObject) parented to a bone based on the camera.
/// This effect is commonly used for 2D sprites for particles and distant objects like trees.
///
/// View plane alignment does not take into account the object's position.
/// This means a rectangular sprite will not appear distorted regardless of its position on screen.
/// For aligning a rectangular sprite to the screen with no distortion, use [BillboardType::XYAxisViewPlaneAligned].
///
/// Viewpoint oriented billboards do take into account the object's position.
/// This means a rectangular sprite positioned on the edges of the screen will appear
/// slightly warped due to perspective projection.
/// For example, [BillboardType::XYAxisViewPointAligned] will appear to point towards the
/// camera axes at the center of the screen in pause mode in Smash Ultimate.
// https://www.flipcode.com/archives/Billboarding-Excerpt_From_iReal-Time_Renderingi_2E.shtml
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "strum", derive(FromRepr, Display, EnumIter, EnumString))]
#[derive(Debug, BinRead, SsbhWrite, Clone, Copy, PartialEq, Eq)]
#[br(repr(u8))]
#[ssbhwrite(repr(u8))]
pub enum BillboardType {
/// Disable billboarding for this bone.
Disabled = 0,
/// The bone rotates along the X-axis to face the camera.
XAxisViewPointAligned = 1, // effected by cam pos + rot
/// The bone rotates along the Y-axis to face the camera.
YAxisViewPointAligned = 2, // effected by cam pos + rot
/// Likely identical to [BillboardType::Disabled]
Unk3 = 3,
/// The bone rotates along the X and Y axes to face the camera.
XYAxisViewPointAligned = 4, // effected by cam pos + rot
/// The bone rotates along the Y-axis to face the camera.
YAxisViewPlaneAligned = 6, // effected by cam rot
/// The bone rotates along the X and Y axes to face the camera.
XYAxisViewPlaneAligned = 8, // effected by cam rot
}