mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 22:20:20 +00:00
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, }, ```
This commit is contained in:
parent
70b0eacc3b
commit
71adb77a2e
3 changed files with 192 additions and 48 deletions
|
@ -21,7 +21,8 @@ use bevy_utils::{tracing::warn, HashMap};
|
|||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
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.
|
||||
pub keyframe_timestamps: Vec<f32>,
|
||||
/// 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,
|
||||
/// 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.
|
||||
|
@ -591,6 +612,18 @@ fn get_keyframe(target_count: usize, keyframes: &[f32], key_index: usize) -> &[f
|
|||
&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)]
|
||||
fn apply_animation(
|
||||
weight: f32,
|
||||
|
@ -645,7 +678,7 @@ fn apply_animation(
|
|||
continue;
|
||||
};
|
||||
// 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 {
|
||||
// Some curves have only one keyframe used to set a transform
|
||||
if curve.keyframe_timestamps.len() == 1 {
|
||||
|
@ -661,7 +694,7 @@ fn apply_animation(
|
|||
transform.scale = transform.scale.lerp(keyframes[0], weight);
|
||||
}
|
||||
Keyframes::Weights(keyframes) => {
|
||||
if let Ok(morphs) = &mut morphs {
|
||||
if let Some(morphs) = &mut morphs {
|
||||
let target_count = morphs.weights().len();
|
||||
lerp_morph_weights(
|
||||
morphs.weights_mut(),
|
||||
|
@ -690,44 +723,15 @@ fn apply_animation(
|
|||
let ts_end = curve.keyframe_timestamps[step_start + 1];
|
||||
let lerp = (animation.seek_time - ts_start) / (ts_end - ts_start);
|
||||
|
||||
// Apply the keyframe
|
||||
match &curve.keyframes {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
apply_keyframe(
|
||||
curve,
|
||||
step_start,
|
||||
weight,
|
||||
lerp,
|
||||
ts_end - ts_start,
|
||||
&mut transform,
|
||||
&mut morphs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
player.transitions.retain_mut(|animation| {
|
||||
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")]
|
||||
let (animations, named_animations, animation_roots) = {
|
||||
use bevy_animation::Keyframes;
|
||||
use bevy_animation::{Interpolation, Keyframes};
|
||||
use gltf::animation::util::ReadOutputs;
|
||||
let mut animations = vec![];
|
||||
let mut named_animations = HashMap::default();
|
||||
|
@ -213,12 +213,10 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||
for animation in gltf.animations() {
|
||||
let mut animation_clip = bevy_animation::AnimationClip::default();
|
||||
for channel in animation.channels() {
|
||||
match channel.sampler().interpolation() {
|
||||
gltf::animation::Interpolation::Linear => (),
|
||||
other => warn!(
|
||||
"Animation interpolation {:?} is not supported, will use linear",
|
||||
other
|
||||
),
|
||||
let interpolation = match channel.sampler().interpolation() {
|
||||
gltf::animation::Interpolation::Linear => Interpolation::Linear,
|
||||
gltf::animation::Interpolation::Step => Interpolation::Step,
|
||||
gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline,
|
||||
};
|
||||
let node = channel.target().node();
|
||||
let reader = channel.reader(|buffer| Some(&buffer_data[buffer.index()]));
|
||||
|
@ -264,6 +262,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||
bevy_animation::VariableCurve {
|
||||
keyframe_timestamps,
|
||||
keyframes,
|
||||
interpolation,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -50,6 +50,7 @@ fn setup(
|
|||
// be the same as the first one
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
]),
|
||||
interpolation: Interpolation::Linear,
|
||||
},
|
||||
);
|
||||
// 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::IDENTITY,
|
||||
]),
|
||||
interpolation: Interpolation::Linear,
|
||||
},
|
||||
);
|
||||
// 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(0.8),
|
||||
]),
|
||||
interpolation: Interpolation::Linear,
|
||||
},
|
||||
);
|
||||
// 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::IDENTITY,
|
||||
]),
|
||||
interpolation: Interpolation::Linear,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue