Skip to content

Commit 71adb77

Browse files
authored
support all types of animation interpolation from gltf (#10755)
# Objective - Support step and cubic spline interpolation from gltf ## Solution - Support step and cubic spline interpolation from gltf Tested with https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/InterpolationTest expected: ![](https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/InterpolationTest/screenshot/screenshot.gif) result: ![output](https://github.com/bevyengine/bevy/assets/8672791/e7f1afd5-20c9-4921-97d4-8d0c82203068) --- ## Migration Guide When manually specifying an animation `VariableCurve`, the interpolation type must be specified: - Bevy 0.12 ```rust VariableCurve { keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0], keyframes: Keyframes::Rotation(vec![ Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::IDENTITY, ]), }, ``` - Bevy 0.13 ```rust VariableCurve { keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0], keyframes: Keyframes::Rotation(vec![ Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::IDENTITY, ]), interpolation: Interpolation::Linear, }, ```
1 parent 70b0eac commit 71adb77

File tree

3 files changed

+192
-48
lines changed

3 files changed

+192
-48
lines changed

crates/bevy_animation/src/lib.rs

Lines changed: 182 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ use bevy_utils::{tracing::warn, HashMap};
2121
pub mod prelude {
2222
#[doc(hidden)]
2323
pub use crate::{
24-
AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Keyframes, VariableCurve,
24+
AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Interpolation, Keyframes,
25+
VariableCurve,
2526
};
2627
}
2728

@@ -53,7 +54,27 @@ pub struct VariableCurve {
5354
/// Timestamp for each of the keyframes.
5455
pub keyframe_timestamps: Vec<f32>,
5556
/// List of the keyframes.
57+
///
58+
/// The representation will depend on the interpolation type of this curve:
59+
///
60+
/// - for `Interpolation::Step` and `Interpolation::Linear`, each keyframe is a single value
61+
/// - for `Interpolation::CubicSpline`, each keyframe is made of three values for `tangent_in`,
62+
/// `keyframe_value` and `tangent_out`
5663
pub keyframes: Keyframes,
64+
/// Interpolation method to use between keyframes.
65+
pub interpolation: Interpolation,
66+
}
67+
68+
/// Interpolation method to use between keyframes.
69+
#[derive(Reflect, Clone, Debug)]
70+
pub enum Interpolation {
71+
/// Linear interpolation between the two closest keyframes.
72+
Linear,
73+
/// Step interpolation, the value of the start keyframe is used.
74+
Step,
75+
/// Cubic spline interpolation. The value of the two closest keyframes is used, with the out
76+
/// tangent of the start keyframe and the in tangent of the end keyframe.
77+
CubicSpline,
5778
}
5879

5980
/// Path to an entity, with [`Name`]s. Each entity in a path must have a name.
@@ -591,6 +612,18 @@ fn get_keyframe(target_count: usize, keyframes: &[f32], key_index: usize) -> &[f
591612
&keyframes[start..end]
592613
}
593614

615+
// Helper macro for cubic spline interpolation
616+
// it needs to work on `f32`, `Vec3` and `Quat`
617+
// TODO: replace by a function if the proper trait bounds can be figured out
618+
macro_rules! cubic_spline_interpolation {
619+
($value_start: expr, $tangent_out_start: expr, $tangent_in_end: expr, $value_end: expr, $lerp: expr, $step_duration: expr,) => {
620+
$value_start * (2.0 * $lerp.powi(3) - 3.0 * $lerp.powi(2) + 1.0)
621+
+ $tangent_out_start * ($step_duration) * ($lerp.powi(3) - 2.0 * $lerp.powi(2) + $lerp)
622+
+ $value_end * (-2.0 * $lerp.powi(3) + 3.0 * $lerp.powi(2))
623+
+ $tangent_in_end * ($step_duration) * ($lerp.powi(3) - $lerp.powi(2))
624+
};
625+
}
626+
594627
#[allow(clippy::too_many_arguments)]
595628
fn apply_animation(
596629
weight: f32,
@@ -645,7 +678,7 @@ fn apply_animation(
645678
continue;
646679
};
647680
// SAFETY: As above, there can't be other AnimationPlayers with this target so this fetch can't alias
648-
let mut morphs = unsafe { morphs.get_unchecked(target) };
681+
let mut morphs = unsafe { morphs.get_unchecked(target) }.ok();
649682
for curve in curves {
650683
// Some curves have only one keyframe used to set a transform
651684
if curve.keyframe_timestamps.len() == 1 {
@@ -661,7 +694,7 @@ fn apply_animation(
661694
transform.scale = transform.scale.lerp(keyframes[0], weight);
662695
}
663696
Keyframes::Weights(keyframes) => {
664-
if let Ok(morphs) = &mut morphs {
697+
if let Some(morphs) = &mut morphs {
665698
let target_count = morphs.weights().len();
666699
lerp_morph_weights(
667700
morphs.weights_mut(),
@@ -690,44 +723,15 @@ fn apply_animation(
690723
let ts_end = curve.keyframe_timestamps[step_start + 1];
691724
let lerp = (animation.seek_time - ts_start) / (ts_end - ts_start);
692725

693-
// Apply the keyframe
694-
match &curve.keyframes {
695-
Keyframes::Rotation(keyframes) => {
696-
let rot_start = keyframes[step_start];
697-
let mut rot_end = keyframes[step_start + 1];
698-
// Choose the smallest angle for the rotation
699-
if rot_end.dot(rot_start) < 0.0 {
700-
rot_end = -rot_end;
701-
}
702-
// Rotations are using a spherical linear interpolation
703-
let rot = rot_start.normalize().slerp(rot_end.normalize(), lerp);
704-
transform.rotation = transform.rotation.slerp(rot, weight);
705-
}
706-
Keyframes::Translation(keyframes) => {
707-
let translation_start = keyframes[step_start];
708-
let translation_end = keyframes[step_start + 1];
709-
let result = translation_start.lerp(translation_end, lerp);
710-
transform.translation = transform.translation.lerp(result, weight);
711-
}
712-
Keyframes::Scale(keyframes) => {
713-
let scale_start = keyframes[step_start];
714-
let scale_end = keyframes[step_start + 1];
715-
let result = scale_start.lerp(scale_end, lerp);
716-
transform.scale = transform.scale.lerp(result, weight);
717-
}
718-
Keyframes::Weights(keyframes) => {
719-
if let Ok(morphs) = &mut morphs {
720-
let target_count = morphs.weights().len();
721-
let morph_start = get_keyframe(target_count, keyframes, step_start);
722-
let morph_end = get_keyframe(target_count, keyframes, step_start + 1);
723-
let result = morph_start
724-
.iter()
725-
.zip(morph_end)
726-
.map(|(a, b)| *a + lerp * (*b - *a));
727-
lerp_morph_weights(morphs.weights_mut(), result, weight);
728-
}
729-
}
730-
}
726+
apply_keyframe(
727+
curve,
728+
step_start,
729+
weight,
730+
lerp,
731+
ts_end - ts_start,
732+
&mut transform,
733+
&mut morphs,
734+
);
731735
}
732736
}
733737

@@ -737,6 +741,143 @@ fn apply_animation(
737741
}
738742
}
739743

744+
#[inline(always)]
745+
fn apply_keyframe(
746+
curve: &VariableCurve,
747+
step_start: usize,
748+
weight: f32,
749+
lerp: f32,
750+
duration: f32,
751+
transform: &mut Mut<Transform>,
752+
morphs: &mut Option<Mut<MorphWeights>>,
753+
) {
754+
match (&curve.interpolation, &curve.keyframes) {
755+
(Interpolation::Step, Keyframes::Rotation(keyframes)) => {
756+
transform.rotation = transform.rotation.slerp(keyframes[step_start], weight);
757+
}
758+
(Interpolation::Linear, Keyframes::Rotation(keyframes)) => {
759+
let rot_start = keyframes[step_start];
760+
let mut rot_end = keyframes[step_start + 1];
761+
// Choose the smallest angle for the rotation
762+
if rot_end.dot(rot_start) < 0.0 {
763+
rot_end = -rot_end;
764+
}
765+
// Rotations are using a spherical linear interpolation
766+
let rot = rot_start.normalize().slerp(rot_end.normalize(), lerp);
767+
transform.rotation = transform.rotation.slerp(rot, weight);
768+
}
769+
(Interpolation::CubicSpline, Keyframes::Rotation(keyframes)) => {
770+
let value_start = keyframes[step_start * 3 + 1];
771+
let tangent_out_start = keyframes[step_start * 3 + 2];
772+
let tangent_in_end = keyframes[(step_start + 1) * 3];
773+
let value_end = keyframes[(step_start + 1) * 3 + 1];
774+
let result = cubic_spline_interpolation!(
775+
value_start,
776+
tangent_out_start,
777+
tangent_in_end,
778+
value_end,
779+
lerp,
780+
duration,
781+
);
782+
transform.rotation = transform.rotation.slerp(result.normalize(), weight);
783+
}
784+
(Interpolation::Step, Keyframes::Translation(keyframes)) => {
785+
transform.translation = transform.translation.lerp(keyframes[step_start], weight);
786+
}
787+
(Interpolation::Linear, Keyframes::Translation(keyframes)) => {
788+
let translation_start = keyframes[step_start];
789+
let translation_end = keyframes[step_start + 1];
790+
let result = translation_start.lerp(translation_end, lerp);
791+
transform.translation = transform.translation.lerp(result, weight);
792+
}
793+
(Interpolation::CubicSpline, Keyframes::Translation(keyframes)) => {
794+
let value_start = keyframes[step_start * 3 + 1];
795+
let tangent_out_start = keyframes[step_start * 3 + 2];
796+
let tangent_in_end = keyframes[(step_start + 1) * 3];
797+
let value_end = keyframes[(step_start + 1) * 3 + 1];
798+
let result = cubic_spline_interpolation!(
799+
value_start,
800+
tangent_out_start,
801+
tangent_in_end,
802+
value_end,
803+
lerp,
804+
duration,
805+
);
806+
transform.translation = transform.translation.lerp(result, weight);
807+
}
808+
(Interpolation::Step, Keyframes::Scale(keyframes)) => {
809+
transform.scale = transform.scale.lerp(keyframes[step_start], weight);
810+
}
811+
(Interpolation::Linear, Keyframes::Scale(keyframes)) => {
812+
let scale_start = keyframes[step_start];
813+
let scale_end = keyframes[step_start + 1];
814+
let result = scale_start.lerp(scale_end, lerp);
815+
transform.scale = transform.scale.lerp(result, weight);
816+
}
817+
(Interpolation::CubicSpline, Keyframes::Scale(keyframes)) => {
818+
let value_start = keyframes[step_start * 3 + 1];
819+
let tangent_out_start = keyframes[step_start * 3 + 2];
820+
let tangent_in_end = keyframes[(step_start + 1) * 3];
821+
let value_end = keyframes[(step_start + 1) * 3 + 1];
822+
let result = cubic_spline_interpolation!(
823+
value_start,
824+
tangent_out_start,
825+
tangent_in_end,
826+
value_end,
827+
lerp,
828+
duration,
829+
);
830+
transform.scale = transform.scale.lerp(result, weight);
831+
}
832+
(Interpolation::Step, Keyframes::Weights(keyframes)) => {
833+
if let Some(morphs) = morphs {
834+
let target_count = morphs.weights().len();
835+
let morph_start = get_keyframe(target_count, keyframes, step_start);
836+
lerp_morph_weights(morphs.weights_mut(), morph_start.iter().copied(), weight);
837+
}
838+
}
839+
(Interpolation::Linear, Keyframes::Weights(keyframes)) => {
840+
if let Some(morphs) = morphs {
841+
let target_count = morphs.weights().len();
842+
let morph_start = get_keyframe(target_count, keyframes, step_start);
843+
let morph_end = get_keyframe(target_count, keyframes, step_start + 1);
844+
let result = morph_start
845+
.iter()
846+
.zip(morph_end)
847+
.map(|(a, b)| *a + lerp * (*b - *a));
848+
lerp_morph_weights(morphs.weights_mut(), result, weight);
849+
}
850+
}
851+
(Interpolation::CubicSpline, Keyframes::Weights(keyframes)) => {
852+
if let Some(morphs) = morphs {
853+
let target_count = morphs.weights().len();
854+
let morph_start = get_keyframe(target_count, keyframes, step_start * 3 + 1);
855+
let tangents_out_start = get_keyframe(target_count, keyframes, step_start * 3 + 2);
856+
let tangents_in_end = get_keyframe(target_count, keyframes, (step_start + 1) * 3);
857+
let morph_end = get_keyframe(target_count, keyframes, (step_start + 1) * 3 + 1);
858+
let result = morph_start
859+
.iter()
860+
.zip(tangents_out_start)
861+
.zip(tangents_in_end)
862+
.zip(morph_end)
863+
.map(
864+
|(((value_start, tangent_out_start), tangent_in_end), value_end)| {
865+
cubic_spline_interpolation!(
866+
value_start,
867+
tangent_out_start,
868+
tangent_in_end,
869+
value_end,
870+
lerp,
871+
duration,
872+
)
873+
},
874+
);
875+
lerp_morph_weights(morphs.weights_mut(), result, weight);
876+
}
877+
}
878+
}
879+
}
880+
740881
fn update_transitions(player: &mut AnimationPlayer, time: &Time) {
741882
player.transitions.retain_mut(|animation| {
742883
animation.current_weight -= animation.weight_decline_per_sec * time.delta_seconds();

crates/bevy_gltf/src/loader.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,20 +205,18 @@ async fn load_gltf<'a, 'b, 'c>(
205205

206206
#[cfg(feature = "bevy_animation")]
207207
let (animations, named_animations, animation_roots) = {
208-
use bevy_animation::Keyframes;
208+
use bevy_animation::{Interpolation, Keyframes};
209209
use gltf::animation::util::ReadOutputs;
210210
let mut animations = vec![];
211211
let mut named_animations = HashMap::default();
212212
let mut animation_roots = HashSet::default();
213213
for animation in gltf.animations() {
214214
let mut animation_clip = bevy_animation::AnimationClip::default();
215215
for channel in animation.channels() {
216-
match channel.sampler().interpolation() {
217-
gltf::animation::Interpolation::Linear => (),
218-
other => warn!(
219-
"Animation interpolation {:?} is not supported, will use linear",
220-
other
221-
),
216+
let interpolation = match channel.sampler().interpolation() {
217+
gltf::animation::Interpolation::Linear => Interpolation::Linear,
218+
gltf::animation::Interpolation::Step => Interpolation::Step,
219+
gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline,
222220
};
223221
let node = channel.target().node();
224222
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
@@ -264,6 +262,7 @@ async fn load_gltf<'a, 'b, 'c>(
264262
bevy_animation::VariableCurve {
265263
keyframe_timestamps,
266264
keyframes,
265+
interpolation,
267266
},
268267
);
269268
} else {

examples/animation/animated_transform.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fn setup(
5050
// be the same as the first one
5151
Vec3::new(1.0, 0.0, 1.0),
5252
]),
53+
interpolation: Interpolation::Linear,
5354
},
5455
);
5556
// Or it can modify the rotation of the transform.
@@ -68,6 +69,7 @@ fn setup(
6869
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
6970
Quat::IDENTITY,
7071
]),
72+
interpolation: Interpolation::Linear,
7173
},
7274
);
7375
// If a curve in an animation is shorter than the other, it will not repeat
@@ -90,6 +92,7 @@ fn setup(
9092
Vec3::splat(1.2),
9193
Vec3::splat(0.8),
9294
]),
95+
interpolation: Interpolation::Linear,
9396
},
9497
);
9598
// There can be more than one curve targeting the same entity path
@@ -106,6 +109,7 @@ fn setup(
106109
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
107110
Quat::IDENTITY,
108111
]),
112+
interpolation: Interpolation::Linear,
109113
},
110114
);
111115

0 commit comments

Comments
 (0)