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:  result:  --- ## 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, }, ```
This commit is contained in:
parent
70b0eacc3b
commit
71adb77a2e
@ -21,7 +21,8 @@ use bevy_utils::{tracing::warn, HashMap};
|
|||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Keyframes, VariableCurve,
|
AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Interpolation, Keyframes,
|
||||||
|
VariableCurve,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +54,27 @@ pub struct VariableCurve {
|
|||||||
/// Timestamp for each of the keyframes.
|
/// Timestamp for each of the keyframes.
|
||||||
pub keyframe_timestamps: Vec<f32>,
|
pub keyframe_timestamps: Vec<f32>,
|
||||||
/// List of the keyframes.
|
/// List of the keyframes.
|
||||||
|
///
|
||||||
|
/// The representation will depend on the interpolation type of this curve:
|
||||||
|
///
|
||||||
|
/// - for `Interpolation::Step` and `Interpolation::Linear`, each keyframe is a single value
|
||||||
|
/// - for `Interpolation::CubicSpline`, each keyframe is made of three values for `tangent_in`,
|
||||||
|
/// `keyframe_value` and `tangent_out`
|
||||||
pub keyframes: Keyframes,
|
pub keyframes: Keyframes,
|
||||||
|
/// Interpolation method to use between keyframes.
|
||||||
|
pub interpolation: Interpolation,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interpolation method to use between keyframes.
|
||||||
|
#[derive(Reflect, Clone, Debug)]
|
||||||
|
pub enum Interpolation {
|
||||||
|
/// Linear interpolation between the two closest keyframes.
|
||||||
|
Linear,
|
||||||
|
/// Step interpolation, the value of the start keyframe is used.
|
||||||
|
Step,
|
||||||
|
/// Cubic spline interpolation. The value of the two closest keyframes is used, with the out
|
||||||
|
/// tangent of the start keyframe and the in tangent of the end keyframe.
|
||||||
|
CubicSpline,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path to an entity, with [`Name`]s. Each entity in a path must have a name.
|
/// 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
|
|||||||
&keyframes[start..end]
|
&keyframes[start..end]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper macro for cubic spline interpolation
|
||||||
|
// it needs to work on `f32`, `Vec3` and `Quat`
|
||||||
|
// TODO: replace by a function if the proper trait bounds can be figured out
|
||||||
|
macro_rules! cubic_spline_interpolation {
|
||||||
|
($value_start: expr, $tangent_out_start: expr, $tangent_in_end: expr, $value_end: expr, $lerp: expr, $step_duration: expr,) => {
|
||||||
|
$value_start * (2.0 * $lerp.powi(3) - 3.0 * $lerp.powi(2) + 1.0)
|
||||||
|
+ $tangent_out_start * ($step_duration) * ($lerp.powi(3) - 2.0 * $lerp.powi(2) + $lerp)
|
||||||
|
+ $value_end * (-2.0 * $lerp.powi(3) + 3.0 * $lerp.powi(2))
|
||||||
|
+ $tangent_in_end * ($step_duration) * ($lerp.powi(3) - $lerp.powi(2))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn apply_animation(
|
fn apply_animation(
|
||||||
weight: f32,
|
weight: f32,
|
||||||
@ -645,7 +678,7 @@ fn apply_animation(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// SAFETY: As above, there can't be other AnimationPlayers with this target so this fetch can't alias
|
// SAFETY: As above, there can't be other AnimationPlayers with this target so this fetch can't alias
|
||||||
let mut morphs = unsafe { morphs.get_unchecked(target) };
|
let mut morphs = unsafe { morphs.get_unchecked(target) }.ok();
|
||||||
for curve in curves {
|
for curve in curves {
|
||||||
// Some curves have only one keyframe used to set a transform
|
// Some curves have only one keyframe used to set a transform
|
||||||
if curve.keyframe_timestamps.len() == 1 {
|
if curve.keyframe_timestamps.len() == 1 {
|
||||||
@ -661,7 +694,7 @@ fn apply_animation(
|
|||||||
transform.scale = transform.scale.lerp(keyframes[0], weight);
|
transform.scale = transform.scale.lerp(keyframes[0], weight);
|
||||||
}
|
}
|
||||||
Keyframes::Weights(keyframes) => {
|
Keyframes::Weights(keyframes) => {
|
||||||
if let Ok(morphs) = &mut morphs {
|
if let Some(morphs) = &mut morphs {
|
||||||
let target_count = morphs.weights().len();
|
let target_count = morphs.weights().len();
|
||||||
lerp_morph_weights(
|
lerp_morph_weights(
|
||||||
morphs.weights_mut(),
|
morphs.weights_mut(),
|
||||||
@ -690,44 +723,15 @@ fn apply_animation(
|
|||||||
let ts_end = curve.keyframe_timestamps[step_start + 1];
|
let ts_end = curve.keyframe_timestamps[step_start + 1];
|
||||||
let lerp = (animation.seek_time - ts_start) / (ts_end - ts_start);
|
let lerp = (animation.seek_time - ts_start) / (ts_end - ts_start);
|
||||||
|
|
||||||
// Apply the keyframe
|
apply_keyframe(
|
||||||
match &curve.keyframes {
|
curve,
|
||||||
Keyframes::Rotation(keyframes) => {
|
step_start,
|
||||||
let rot_start = keyframes[step_start];
|
weight,
|
||||||
let mut rot_end = keyframes[step_start + 1];
|
lerp,
|
||||||
// Choose the smallest angle for the rotation
|
ts_end - ts_start,
|
||||||
if rot_end.dot(rot_start) < 0.0 {
|
&mut transform,
|
||||||
rot_end = -rot_end;
|
&mut morphs,
|
||||||
}
|
);
|
||||||
// Rotations are using a spherical linear interpolation
|
|
||||||
let rot = rot_start.normalize().slerp(rot_end.normalize(), lerp);
|
|
||||||
transform.rotation = transform.rotation.slerp(rot, weight);
|
|
||||||
}
|
|
||||||
Keyframes::Translation(keyframes) => {
|
|
||||||
let translation_start = keyframes[step_start];
|
|
||||||
let translation_end = keyframes[step_start + 1];
|
|
||||||
let result = translation_start.lerp(translation_end, lerp);
|
|
||||||
transform.translation = transform.translation.lerp(result, weight);
|
|
||||||
}
|
|
||||||
Keyframes::Scale(keyframes) => {
|
|
||||||
let scale_start = keyframes[step_start];
|
|
||||||
let scale_end = keyframes[step_start + 1];
|
|
||||||
let result = scale_start.lerp(scale_end, lerp);
|
|
||||||
transform.scale = transform.scale.lerp(result, weight);
|
|
||||||
}
|
|
||||||
Keyframes::Weights(keyframes) => {
|
|
||||||
if let Ok(morphs) = &mut morphs {
|
|
||||||
let target_count = morphs.weights().len();
|
|
||||||
let morph_start = get_keyframe(target_count, keyframes, step_start);
|
|
||||||
let morph_end = get_keyframe(target_count, keyframes, step_start + 1);
|
|
||||||
let result = morph_start
|
|
||||||
.iter()
|
|
||||||
.zip(morph_end)
|
|
||||||
.map(|(a, b)| *a + lerp * (*b - *a));
|
|
||||||
lerp_morph_weights(morphs.weights_mut(), result, weight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,6 +741,143 @@ fn apply_animation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn apply_keyframe(
|
||||||
|
curve: &VariableCurve,
|
||||||
|
step_start: usize,
|
||||||
|
weight: f32,
|
||||||
|
lerp: f32,
|
||||||
|
duration: f32,
|
||||||
|
transform: &mut Mut<Transform>,
|
||||||
|
morphs: &mut Option<Mut<MorphWeights>>,
|
||||||
|
) {
|
||||||
|
match (&curve.interpolation, &curve.keyframes) {
|
||||||
|
(Interpolation::Step, Keyframes::Rotation(keyframes)) => {
|
||||||
|
transform.rotation = transform.rotation.slerp(keyframes[step_start], weight);
|
||||||
|
}
|
||||||
|
(Interpolation::Linear, Keyframes::Rotation(keyframes)) => {
|
||||||
|
let rot_start = keyframes[step_start];
|
||||||
|
let mut rot_end = keyframes[step_start + 1];
|
||||||
|
// Choose the smallest angle for the rotation
|
||||||
|
if rot_end.dot(rot_start) < 0.0 {
|
||||||
|
rot_end = -rot_end;
|
||||||
|
}
|
||||||
|
// Rotations are using a spherical linear interpolation
|
||||||
|
let rot = rot_start.normalize().slerp(rot_end.normalize(), lerp);
|
||||||
|
transform.rotation = transform.rotation.slerp(rot, weight);
|
||||||
|
}
|
||||||
|
(Interpolation::CubicSpline, Keyframes::Rotation(keyframes)) => {
|
||||||
|
let value_start = keyframes[step_start * 3 + 1];
|
||||||
|
let tangent_out_start = keyframes[step_start * 3 + 2];
|
||||||
|
let tangent_in_end = keyframes[(step_start + 1) * 3];
|
||||||
|
let value_end = keyframes[(step_start + 1) * 3 + 1];
|
||||||
|
let result = cubic_spline_interpolation!(
|
||||||
|
value_start,
|
||||||
|
tangent_out_start,
|
||||||
|
tangent_in_end,
|
||||||
|
value_end,
|
||||||
|
lerp,
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
transform.rotation = transform.rotation.slerp(result.normalize(), weight);
|
||||||
|
}
|
||||||
|
(Interpolation::Step, Keyframes::Translation(keyframes)) => {
|
||||||
|
transform.translation = transform.translation.lerp(keyframes[step_start], weight);
|
||||||
|
}
|
||||||
|
(Interpolation::Linear, Keyframes::Translation(keyframes)) => {
|
||||||
|
let translation_start = keyframes[step_start];
|
||||||
|
let translation_end = keyframes[step_start + 1];
|
||||||
|
let result = translation_start.lerp(translation_end, lerp);
|
||||||
|
transform.translation = transform.translation.lerp(result, weight);
|
||||||
|
}
|
||||||
|
(Interpolation::CubicSpline, Keyframes::Translation(keyframes)) => {
|
||||||
|
let value_start = keyframes[step_start * 3 + 1];
|
||||||
|
let tangent_out_start = keyframes[step_start * 3 + 2];
|
||||||
|
let tangent_in_end = keyframes[(step_start + 1) * 3];
|
||||||
|
let value_end = keyframes[(step_start + 1) * 3 + 1];
|
||||||
|
let result = cubic_spline_interpolation!(
|
||||||
|
value_start,
|
||||||
|
tangent_out_start,
|
||||||
|
tangent_in_end,
|
||||||
|
value_end,
|
||||||
|
lerp,
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
transform.translation = transform.translation.lerp(result, weight);
|
||||||
|
}
|
||||||
|
(Interpolation::Step, Keyframes::Scale(keyframes)) => {
|
||||||
|
transform.scale = transform.scale.lerp(keyframes[step_start], weight);
|
||||||
|
}
|
||||||
|
(Interpolation::Linear, Keyframes::Scale(keyframes)) => {
|
||||||
|
let scale_start = keyframes[step_start];
|
||||||
|
let scale_end = keyframes[step_start + 1];
|
||||||
|
let result = scale_start.lerp(scale_end, lerp);
|
||||||
|
transform.scale = transform.scale.lerp(result, weight);
|
||||||
|
}
|
||||||
|
(Interpolation::CubicSpline, Keyframes::Scale(keyframes)) => {
|
||||||
|
let value_start = keyframes[step_start * 3 + 1];
|
||||||
|
let tangent_out_start = keyframes[step_start * 3 + 2];
|
||||||
|
let tangent_in_end = keyframes[(step_start + 1) * 3];
|
||||||
|
let value_end = keyframes[(step_start + 1) * 3 + 1];
|
||||||
|
let result = cubic_spline_interpolation!(
|
||||||
|
value_start,
|
||||||
|
tangent_out_start,
|
||||||
|
tangent_in_end,
|
||||||
|
value_end,
|
||||||
|
lerp,
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
transform.scale = transform.scale.lerp(result, weight);
|
||||||
|
}
|
||||||
|
(Interpolation::Step, Keyframes::Weights(keyframes)) => {
|
||||||
|
if let Some(morphs) = morphs {
|
||||||
|
let target_count = morphs.weights().len();
|
||||||
|
let morph_start = get_keyframe(target_count, keyframes, step_start);
|
||||||
|
lerp_morph_weights(morphs.weights_mut(), morph_start.iter().copied(), weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Interpolation::Linear, Keyframes::Weights(keyframes)) => {
|
||||||
|
if let Some(morphs) = morphs {
|
||||||
|
let target_count = morphs.weights().len();
|
||||||
|
let morph_start = get_keyframe(target_count, keyframes, step_start);
|
||||||
|
let morph_end = get_keyframe(target_count, keyframes, step_start + 1);
|
||||||
|
let result = morph_start
|
||||||
|
.iter()
|
||||||
|
.zip(morph_end)
|
||||||
|
.map(|(a, b)| *a + lerp * (*b - *a));
|
||||||
|
lerp_morph_weights(morphs.weights_mut(), result, weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Interpolation::CubicSpline, Keyframes::Weights(keyframes)) => {
|
||||||
|
if let Some(morphs) = morphs {
|
||||||
|
let target_count = morphs.weights().len();
|
||||||
|
let morph_start = get_keyframe(target_count, keyframes, step_start * 3 + 1);
|
||||||
|
let tangents_out_start = get_keyframe(target_count, keyframes, step_start * 3 + 2);
|
||||||
|
let tangents_in_end = get_keyframe(target_count, keyframes, (step_start + 1) * 3);
|
||||||
|
let morph_end = get_keyframe(target_count, keyframes, (step_start + 1) * 3 + 1);
|
||||||
|
let result = morph_start
|
||||||
|
.iter()
|
||||||
|
.zip(tangents_out_start)
|
||||||
|
.zip(tangents_in_end)
|
||||||
|
.zip(morph_end)
|
||||||
|
.map(
|
||||||
|
|(((value_start, tangent_out_start), tangent_in_end), value_end)| {
|
||||||
|
cubic_spline_interpolation!(
|
||||||
|
value_start,
|
||||||
|
tangent_out_start,
|
||||||
|
tangent_in_end,
|
||||||
|
value_end,
|
||||||
|
lerp,
|
||||||
|
duration,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
lerp_morph_weights(morphs.weights_mut(), result, weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update_transitions(player: &mut AnimationPlayer, time: &Time) {
|
fn update_transitions(player: &mut AnimationPlayer, time: &Time) {
|
||||||
player.transitions.retain_mut(|animation| {
|
player.transitions.retain_mut(|animation| {
|
||||||
animation.current_weight -= animation.weight_decline_per_sec * time.delta_seconds();
|
animation.current_weight -= animation.weight_decline_per_sec * time.delta_seconds();
|
||||||
|
@ -205,7 +205,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
|
|
||||||
#[cfg(feature = "bevy_animation")]
|
#[cfg(feature = "bevy_animation")]
|
||||||
let (animations, named_animations, animation_roots) = {
|
let (animations, named_animations, animation_roots) = {
|
||||||
use bevy_animation::Keyframes;
|
use bevy_animation::{Interpolation, Keyframes};
|
||||||
use gltf::animation::util::ReadOutputs;
|
use gltf::animation::util::ReadOutputs;
|
||||||
let mut animations = vec![];
|
let mut animations = vec![];
|
||||||
let mut named_animations = HashMap::default();
|
let mut named_animations = HashMap::default();
|
||||||
@ -213,12 +213,10 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
for animation in gltf.animations() {
|
for animation in gltf.animations() {
|
||||||
let mut animation_clip = bevy_animation::AnimationClip::default();
|
let mut animation_clip = bevy_animation::AnimationClip::default();
|
||||||
for channel in animation.channels() {
|
for channel in animation.channels() {
|
||||||
match channel.sampler().interpolation() {
|
let interpolation = match channel.sampler().interpolation() {
|
||||||
gltf::animation::Interpolation::Linear => (),
|
gltf::animation::Interpolation::Linear => Interpolation::Linear,
|
||||||
other => warn!(
|
gltf::animation::Interpolation::Step => Interpolation::Step,
|
||||||
"Animation interpolation {:?} is not supported, will use linear",
|
gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline,
|
||||||
other
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
let node = channel.target().node();
|
let node = channel.target().node();
|
||||||
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
||||||
@ -264,6 +262,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
bevy_animation::VariableCurve {
|
bevy_animation::VariableCurve {
|
||||||
keyframe_timestamps,
|
keyframe_timestamps,
|
||||||
keyframes,
|
keyframes,
|
||||||
|
interpolation,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -50,6 +50,7 @@ fn setup(
|
|||||||
// be the same as the first one
|
// be the same as the first one
|
||||||
Vec3::new(1.0, 0.0, 1.0),
|
Vec3::new(1.0, 0.0, 1.0),
|
||||||
]),
|
]),
|
||||||
|
interpolation: Interpolation::Linear,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Or it can modify the rotation of the transform.
|
// Or it can modify the rotation of the transform.
|
||||||
@ -68,6 +69,7 @@ fn setup(
|
|||||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||||
Quat::IDENTITY,
|
Quat::IDENTITY,
|
||||||
]),
|
]),
|
||||||
|
interpolation: Interpolation::Linear,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// If a curve in an animation is shorter than the other, it will not repeat
|
// If a curve in an animation is shorter than the other, it will not repeat
|
||||||
@ -90,6 +92,7 @@ fn setup(
|
|||||||
Vec3::splat(1.2),
|
Vec3::splat(1.2),
|
||||||
Vec3::splat(0.8),
|
Vec3::splat(0.8),
|
||||||
]),
|
]),
|
||||||
|
interpolation: Interpolation::Linear,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// There can be more than one curve targeting the same entity path
|
// There can be more than one curve targeting the same entity path
|
||||||
@ -106,6 +109,7 @@ fn setup(
|
|||||||
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
||||||
Quat::IDENTITY,
|
Quat::IDENTITY,
|
||||||
]),
|
]),
|
||||||
|
interpolation: Interpolation::Linear,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user