animation fixes

This commit is contained in:
Robin Appelman 2024-12-25 17:28:16 +01:00
commit 2309050a25
10 changed files with 397 additions and 66 deletions

View file

@ -1,32 +1,116 @@
use crate::mdl::Mdl;
use crate::mdl::{Bone, BoneId, Mdl};
use std::collections::VecDeque;
use std::ops::Deref;
/// A handle represents a mdl structure in the mdl file and the mdl file containing it.
///
/// Keeping a reference of the mdl file with the mdl is required since a lot of mdl types
/// reference parts from other structures in the mdl file
#[derive(Debug)]
pub struct Handle<'a, T> {
_mdl: &'a Mdl,
#[derive(Debug, Clone)]
pub struct Handle<'a, T, K> {
mdl: &'a Mdl,
data: &'a T,
key: K,
}
impl<T> Clone for Handle<'_, T> {
fn clone(&self) -> Self {
Handle { ..*self }
impl<T, K: PartialEq> PartialEq for Handle<'_, T, K> {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl<'a, T> AsRef<T> for Handle<'a, T> {
impl<'a, T, K> Handle<'a, T, K> {
pub fn new(mdl: &'a Mdl, data: &'a T, key: K) -> Self {
Self { mdl, data, key }
}
}
impl<T, K: Clone> Handle<'_, T, K> {
pub fn key(&self) -> K {
self.key.clone()
}
}
impl<'a, T, K> AsRef<T> for Handle<'a, T, K> {
fn as_ref(&self) -> &'a T {
self.data
}
}
impl<T> Deref for Handle<'_, T> {
impl<T, K> Deref for Handle<'_, T, K> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data
}
}
impl<'a> Handle<'a, Bone, BoneId> {
pub fn parent(&self) -> Option<Self> {
Some(Self::new(
self.mdl,
self.mdl.bones.get(usize::from(self.parent))?,
self.parent,
))
}
pub fn children(&self) -> impl Iterator<Item = Self> + 'a {
let key = self.key();
let mdl = self.mdl;
self.mdl
.bones
.iter()
.enumerate()
.filter(move |(_, bone)| bone.parent == key)
.map(move |(i, bone)| Self::new(mdl, bone, i.into()))
}
pub fn tree(&self) -> impl Iterator<Item = Self> {
BoneTreeIter::new(self.clone())
}
pub fn ancestors(&self) -> impl Iterator<Item = Self> {
BoneAncestorsIter { bone: self.clone() }
}
pub fn is_affected_by(&self, bone_id: BoneId) -> bool {
self.key == bone_id || self.ancestors().any(|ancestor| ancestor.key == bone_id)
}
}
struct BoneTreeIter<'a> {
queue: VecDeque<Handle<'a, Bone, BoneId>>,
}
impl<'a> BoneTreeIter<'a> {
pub fn new(root: Handle<'a, Bone, BoneId>) -> Self {
let mut queue = VecDeque::with_capacity(16);
queue.push_back(root);
BoneTreeIter { queue }
}
}
impl<'a> Iterator for BoneTreeIter<'a> {
type Item = Handle<'a, Bone, BoneId>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.queue.pop_front()?;
self.queue.extend(next.children());
Some(next)
}
}
struct BoneAncestorsIter<'a> {
bone: Handle<'a, Bone, BoneId>,
}
impl<'a> Iterator for BoneAncestorsIter<'a> {
type Item = Handle<'a, Bone, BoneId>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.bone.parent()?;
self.bone = next.clone();
Some(next)
}
}

View file

@ -7,7 +7,9 @@ pub mod vtx;
pub mod vvd;
pub use crate::mdl::Mdl;
use crate::mdl::{AnimationDescription, Bone, ModelFlags, PoseParameterDescription, TextureInfo};
use crate::mdl::{
AnimationDescription, Bone, BoneId, ModelFlags, PoseParameterDescription, TextureInfo,
};
pub use crate::vtx::Vtx;
use crate::vvd::Vertex;
pub use crate::vvd::Vvd;
@ -134,8 +136,19 @@ impl Model {
self.mdl.name.as_str()
}
pub fn bones(&self) -> impl Iterator<Item = &Bone> {
self.mdl.bones.iter()
pub fn bones(&self) -> impl Iterator<Item = Handle<Bone, BoneId>> {
self.mdl
.bones
.iter()
.enumerate()
.map(|(i, bone)| Handle::new(&self.mdl, bone, i.into()))
}
pub fn bone(&self, id: BoneId) -> Option<Handle<Bone, BoneId>> {
self.mdl
.bones
.get(usize::from(id))
.map(|bone| Handle::new(&self.mdl, bone, id))
}
pub fn root_transform(&self) -> Matrix4<f32> {
@ -157,7 +170,11 @@ impl Model {
self.mdl
.local_animations
.iter()
.filter_map(|desc| desc.animations.iter().find(|animation| animation.bone == 0))
.filter_map(|desc| {
desc.animations
.iter()
.find(|animation| animation.bone == BoneId::default())
})
.next()
.map(|animation| animation.rotation(0))
.map(Matrix4::from)
@ -176,6 +193,38 @@ impl Model {
let transform = self.idle_transform() * self.root_transform();
transform.transform_vector(Vector3::from(vec)).into()
}
pub fn apply_animation(
&self,
animation: &AnimationDescription,
vertex: &Vertex,
frame: usize,
) -> Vector {
let mut position = vertex.position.into();
for animation in animation.animations.iter() {
if let Some(animated_bone) = self.bone(animation.bone) {
let weight: f32 = vertex
.bone_weights
.weights()
.flat_map(|weight| Some((self.bone(weight.bone_id)?, weight)))
.filter(|(bone, _)| bone.is_affected_by(animated_bone.key()))
.map(|(_, weight)| weight.weight)
.sum();
let pose_to_bone = animated_bone.pos.into();
let bone_rotation = Matrix4::from(animated_bone.rot);
if weight > 0.0 {
position -= pose_to_bone;
let transform = (animation.transform(frame)) * bone_rotation;
position = transform.transform_vector(position);
position += pose_to_bone;
}
}
}
position.into()
}
}
pub struct SkinTable<'a> {

View file

@ -73,8 +73,8 @@ impl Mdl {
.iter_mut()
.flat_map(|desc| desc.animations.iter_mut())
.for_each(|animation| {
if let Some(bone) = bones.get(animation.bone as usize) {
animation.set_scales(bone);
if let Some(bone) = bones.get(usize::from(animation.bone)) {
animation.apply_bone_data(bone);
}
});
let animation_block_source: String = read_single(data, header.anim_blocks_name_index)?;

View file

@ -1,12 +1,12 @@
use crate::compressed_vector::{Quaternion48, Quaternion64, Vector48};
use crate::mdl::Bone;
use crate::mdl::{Bone, BoneId};
use crate::{
index_range, read_relative, read_single, ModelError, Quaternion, RadianEuler, ReadRelative,
Readable, ReadableRelative, Vector,
};
use bitflags::bitflags;
use bytemuck::{Pod, Zeroable};
use cgmath::{Matrix4, SquareMatrix};
use cgmath::Matrix4;
use std::mem::size_of;
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
@ -89,16 +89,6 @@ pub struct AnimationDescription {
pub animations: Vec<Animation>,
}
impl AnimationDescription {
pub fn get_bone_transform(&self, bone: u8, frame: usize) -> Matrix4<f32> {
let Some(animation) = self.animations.iter().find(|anim| anim.bone == bone) else {
return Matrix4::identity();
};
Matrix4::from_translation(animation.position(frame).into())
* Matrix4::from(animation.rotation(frame))
}
}
impl ReadRelative for AnimationDescription {
type Header = AnimationDescriptionHeader;
@ -139,7 +129,7 @@ impl ReadableRelative for AnimationBlock {}
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
pub struct AnimationHeader {
bone: u8,
bone: BoneId,
flags: AnimationFlags,
next_offset: u16,
}
@ -215,7 +205,7 @@ struct FrameValues<'a> {
}
impl FrameValues<'_> {
pub fn get(&self, index: u8) -> Result<u16, ModelError> {
pub fn get(&self, index: u8) -> Result<i16, ModelError> {
if self.header.total <= index {
let offset_count = self.header.valid + 1;
let offset = (offset_count as usize) * size_of::<u16>();
@ -292,14 +282,25 @@ impl RotationData {
}
}
fn set_scale(&mut self, scale: Vector) {
fn set_scale(&mut self, scale: RadianEuler) {
if let RotationData::Animated(values) = self {
values.iter_mut().for_each(|value| {
// scale and fixup the angles
*value = RadianEuler {
y: value.x * scale.x,
z: value.y * scale.y,
x: value.z * scale.z,
x: value.x * scale.x,
y: value.y * scale.y,
z: value.z * scale.z,
}
});
}
}
fn set_base_rotation(&mut self, base: RadianEuler) {
if let RotationData::Animated(values) = self {
values.iter_mut().for_each(|value| {
*value = RadianEuler {
x: value.x + base.x,
y: value.y + base.y,
z: value.z + base.z,
}
});
}
@ -338,7 +339,7 @@ impl PositionData {
/// Per bone animation data
#[derive(Clone, Debug)]
pub struct Animation {
pub bone: u8,
pub bone: BoneId,
pub flags: AnimationFlags,
rotation_data: RotationData,
position_data: PositionData,
@ -353,8 +354,15 @@ impl Animation {
self.position_data.position(frame)
}
pub(crate) fn set_scales(&mut self, bone: &Bone) {
pub fn transform(&self, frame: usize) -> Matrix4<f32> {
Matrix4::from_translation(self.position(frame).into()) * Matrix4::from(self.rotation(frame))
}
pub(crate) fn apply_bone_data(&mut self, bone: &Bone) {
self.rotation_data.set_scale(bone.rot_scale);
if self.flags.contains(AnimationFlags::STUDIO_ANIM_DELTA) {
self.rotation_data.set_base_rotation(bone.rot);
}
self.position_data.set_scale(bone.pos_scale);
}
}
@ -381,7 +389,7 @@ fn read_animation(
let value_data = &data[offset..];
let values: Vec<RadianEuler> = (0..frames)
.map(|frame| read_animation_values(value_data, frame, pointers))
.map(|r| r.map(|[x, y, z]| RadianEuler { x, z, y }))
.map(|r| r.map(|[y, z, x]| RadianEuler { x, z, y }))
.collect::<Result<_, ModelError>>()?;
RotationData::from(values)
} else {

View file

@ -4,8 +4,43 @@ use crate::{
use bitflags::bitflags;
use bytemuck::{Pod, Zeroable};
use num_enum::TryFromPrimitive;
use std::fmt::Display;
use std::mem::size_of;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Zeroable, Pod, Default)]
#[repr(transparent)]
pub struct BoneId(u8);
impl From<u8> for BoneId {
fn from(val: u8) -> Self {
BoneId(val)
}
}
impl From<i32> for BoneId {
fn from(val: i32) -> Self {
BoneId(u8::try_from(val).unwrap_or(255))
}
}
impl From<usize> for BoneId {
fn from(val: usize) -> Self {
BoneId(u8::try_from(val).unwrap_or_default())
}
}
impl From<BoneId> for usize {
fn from(val: BoneId) -> Self {
val.0 as usize
}
}
impl Display for BoneId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
pub struct BoneHeader {
@ -17,7 +52,7 @@ pub struct BoneHeader {
pub quaternion: Quaternion,
pub rot: RadianEuler,
pub pos_scale: Vector,
pub rot_scale: Vector,
pub rot_scale: [f32; 3],
pub pose_to_bone: Transform3x4,
pub q_alignment: Quaternion,
@ -38,14 +73,14 @@ static_assertions::const_assert_eq!(size_of::<BoneHeader>(), 216);
#[repr(C)]
pub struct Bone {
pub name: String,
pub parent: i32, // parent bone
pub parent: BoneId,
pub bone_controller: [i32; 6], // bone controller index, -1 == none
pub pos: Vector,
pub quaternion: Quaternion,
pub rot: RadianEuler,
pub pos_scale: Vector,
pub rot_scale: Vector,
pub rot_scale: RadianEuler,
pub pose_to_bone: Transform3x4,
pub q_alignment: Quaternion,
@ -93,13 +128,21 @@ impl ReadRelative for Bone {
Ok(Bone {
name: read_single(data, header.sz_name_index)?,
parent: header.parent,
parent: header.parent.into(),
bone_controller: header.bone_controller,
pos: header.pos,
pos: Vector {
x: header.pos.z,
y: header.pos.x,
z: header.pos.y,
},
quaternion: header.quaternion,
rot: header.rot,
pos_scale: header.pos_scale,
rot_scale: header.rot_scale,
rot_scale: RadianEuler {
x: header.rot_scale[2],
y: header.rot_scale[0],
z: header.rot_scale[1],
},
pose_to_bone: header.pose_to_bone,
q_alignment: header.q_alignment,
flags: header.flags,

View file

@ -129,7 +129,7 @@ impl From<cgmath::Quaternion<f32>> for Quaternion {
}
}
impl From<Quaternion> for cgmath::Matrix4<f32> {
impl From<Quaternion> for Matrix4<f32> {
fn from(q: Quaternion) -> Self {
// cgmath::Quaternion::from(Quaternion {
// x: q.z,
@ -240,6 +240,18 @@ impl From<RadianEuler> for Matrix4<f32> {
}
}
impl Mul<f32> for RadianEuler {
type Output = RadianEuler;
fn mul(self, rhs: f32) -> Self::Output {
RadianEuler {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}
/// Fixed length, null-terminated string
#[derive(Debug, Clone, Default, Copy)]
pub struct FixedString<const LEN: usize>(ArrayString<LEN>);

View file

@ -1,3 +1,4 @@
use crate::mdl::BoneId;
use crate::{index_range, ReadableRelative, Vector};
use bytemuck::{Pod, Zeroable};
use std::cmp::min;
@ -82,22 +83,33 @@ static_assertions::const_assert_eq!(size_of::<Vertex>(), 48);
#[repr(C)]
pub struct BoneWeights {
weight: [f32; 3],
bone: [u8; 3],
bone: [BoneId; 3],
bone_count: u8,
}
impl BoneWeights {
pub fn weights(&self) -> impl Iterator<Item = BoneWeight> + '_ {
(0..min(self.bone_count as usize, 3)).map(|i| BoneWeight {
weight: self.weight[i] / self.bone_count as f32,
bone_id: self.bone[i],
})
self.bone
.into_iter()
.zip(self.weight)
.take(min(self.bone_count as usize, 3))
.map(|(bone_id, weight)| BoneWeight {
bone_id,
weight: weight / self.bone_count as f32,
})
}
pub fn get_weight(&self, bone_id: BoneId) -> f32 {
self.weights()
.find(|weight| weight.bone_id == bone_id)
.map(|weight| weight.weight)
.unwrap_or_default()
}
}
#[derive(Clone, Debug)]
pub struct BoneWeight {
pub bone_id: u8,
pub bone_id: BoneId,
pub weight: f32,
}