Skip to content

Commit

Permalink
Add skeletal animations support
Browse files Browse the repository at this point in the history
  • Loading branch information
voxelias committed Dec 3, 2024
1 parent feba388 commit 59d3eb2
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 41 deletions.
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ pub mod math;
/// Models abstractions
pub mod models;
pub use models::{
Animation, Armature, Color, Image, Joint, Material, Mesh, Model, RenderModels, Transform,
VertexAttribute, VertexBitangent, VertexJoints, VertexNormal, VertexPosition, VertexTangent,
VertexTexture, VertexWeights,
Animation, AnimationPlayer, AnimationState, Armature, Color, Image, Joint, Material, Mesh,
Model, RenderModels, Transform, VertexAttribute, VertexBitangent, VertexJoints, VertexNormal,
VertexPosition, VertexTangent, VertexTexture, VertexWeights,
};

/// Rendering tools and routines
Expand Down
15 changes: 12 additions & 3 deletions src/models.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod animations;
pub use animations::{Animation, Interpolation};
pub use animations::{Animation, AnimationPlayer, AnimationState, Interpolation};

mod armatures;
pub use armatures::{Armature, Joint};
Expand Down Expand Up @@ -42,19 +42,27 @@ pub struct Model {
pub scale: Vec3,
pub rotate: Quat,
pub pose: Vec<Mat4>,
pub animation: Option<AnimationPlayer>,
}

impl From<Model> for Entity {
fn from(model: Model) -> Self {
Entity::new((
let animation = model.animation;
let mut entity = Entity::new((
model.mesh,
model.material,
model.armature,
Transform::new(
Transform3D::new(model.translate, model.rotate, model.scale),
model.pose,
),
))
));

if let Some(player) = animation {
entity = entity.with(player);
}

entity
}
}

Expand All @@ -68,6 +76,7 @@ impl Default for Model {
scale: Vec3::new(1.0, 1.0, 1.0),
rotate: Quat::IDENTITY,
pose: Vec::new(),
animation: None,
}
}
}
135 changes: 134 additions & 1 deletion src/models/animations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ impl Animation {
}
}

pub fn name(&self) -> &str {
self.name.as_str()
}

/// Returns [`Duration`] of the animation
pub fn duration(&self) -> Duration {
self.duration
Expand Down Expand Up @@ -88,8 +92,15 @@ impl Animation {

/// Samples the animeation at some keyframe (s) and returns a HashMap of
/// [`crate::assets::Skin`] joint id to [`TransformBuilder`]
pub fn sample(&self, keyframe: f32) -> HashMap<Id<Joint>, TransformBuilder> {
pub fn sample(&self, timestamp: f32) -> HashMap<Id<Joint>, TransformBuilder> {
let mut result = HashMap::new();
let duration_secs = self.duration.as_secs_f32();

let keyframe = if timestamp > duration_secs {
timestamp % duration_secs
} else {
timestamp
};

for channel in &self.translation_channels {
if let Some(transform) = channel.sample(keyframe) {
Expand Down Expand Up @@ -220,3 +231,125 @@ impl<T: Interpolate + Copy + Clone> Channel<T> {
None
}
}

/// Animation player state
///
/// [`Duration`] contains current time offset from the beginning of the animation
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum AnimationState {
/// Animation is playing
Play(Duration),
/// Animation is looped
Loop(Duration),
/// Animation is stopped
Stop,
}

impl AnimationState {
pub fn play() -> Self {
AnimationState::Play(Duration::from_secs(0))
}

pub fn play_loop() -> Self {
AnimationState::Loop(Duration::from_secs(0))
}

pub fn stop() -> Self {
AnimationState::Stop
}
}

/// Component to control model animation
pub struct AnimationPlayer {
animation: Id<Animation>,
state: AnimationState,
/// animation speed
speed: f32,
}

impl AnimationPlayer {
/// creates new component instance with specified animation with [`State::Stop`]
pub fn new(animation: Id<Animation>) -> Self {
Self {
animation,
state: AnimationState::stop(),
speed: 1.0,
}
}

/// creates new component instance with specified animation with [`State::Play`]
pub fn play(animation: Id<Animation>) -> Self {
Self {
animation,
state: AnimationState::play(),
speed: 1.0,
}
}

/// creates new component instance with specified animation with [`State::Loop`]
pub fn looped(animation: Id<Animation>) -> Self {
Self {
animation,
state: AnimationState::play_loop(),
speed: 1.0,
}
}

/// Starts current animation
pub fn start(&mut self) {
self.state = AnimationState::play();
}

/// Starts current animation looped
pub fn start_loop(&mut self) {
self.state = AnimationState::play_loop();
}

/// Stops current animation
pub fn stop(&mut self) {
self.state = AnimationState::stop();
}

/// Changes current animation
pub fn animate(&mut self, animation: Id<Animation>) {
self.animation = animation;
self.state = AnimationState::stop();
}

/// Returns current animation [`Id`]
pub fn animation(&self) -> Id<Animation> {
self.animation
}

/// Returns current [`State`]
pub fn state(&self) -> AnimationState {
self.state
}

pub fn update(&mut self, delta: Duration, duration: Duration) -> Option<Duration> {
let (state, duration) = match self.state {
AnimationState::Play(current) => {
let new_duration = current + delta.mul_f32(self.speed);
let state = if new_duration < duration {
AnimationState::Play(new_duration)
} else {
AnimationState::Stop
};
(state, Some(new_duration))
}
AnimationState::Loop(current) => {
let mut new_duration = current + delta.mul_f32(self.speed);
if !(new_duration < duration) {
new_duration = Duration::from_secs_f32(
new_duration.as_secs_f32() % duration.as_secs_f32(),
);
}
let state = AnimationState::Loop(new_duration);
(state, Some(new_duration))
}
AnimationState::Stop => (AnimationState::Stop, None),
};
self.state = state;
duration
}
}
34 changes: 0 additions & 34 deletions src/models/armatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ impl Armature {

pub fn transform(
&self,
// model_transform: &Mat4,
joint_local_transforms: Option<HashMap<Id<Joint>, TransformBuilder>>,
) -> Vec<Mat4> {
let mut result = vec![Mat4::IDENTITY; self.joints.len()];
Expand All @@ -59,11 +58,6 @@ impl Armature {
.matrix();

result[joint_index] = global_joint_transform;
// joint
// .inverse_bind_matrix
// .as_ref()
// .map(|&inverse_bind_matrix| global_joint_transform * inverse_bind_matrix)
// .unwrap_or(global_joint_transform);
}

result
Expand All @@ -84,34 +78,6 @@ impl Armature {
.collect::<Vec<_>>()
}
}
/*
pub fn transform(
&self,
skin_transform: &mut Pose,
model_transform: &Mat4,
local_transforms: Option<HashMap<JointId, TransformBuilder>>,
) {
for (i, joint) in self.joints.iter().enumerate() {
let parent_transform = joint
.parent_id
.map(|parent_id| skin_transform.joints[self.index(parent_id)].global_transform)
.or(Some(*model_transform))
.unwrap();
let local_transform = local_transforms
.as_ref()
.map(|l| l.get(&joint.id))
.unwrap_or(None);
if i < skin_transform.joints.len() {
skin_transform.joints[i] = joint.transform(&parent_transform, local_transform);
} else {
panic!("Joints count exceeds limit of {:?}", MAX_JOINTS);
}
}
}
*/

#[derive(Default, Debug, Clone)]
/// Joint data structure
Expand Down

0 comments on commit 59d3eb2

Please sign in to comment.