Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unified Transform blend stack #15665

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,17 @@ description = "Skinned mesh example with mesh and joints data loaded from a glTF
category = "Animation"
wasm = true

[[example]]
name = "transform_curve_animation"
path = "examples/animation/transform_curve_animation.rs"
doc-scrape-examples = true

[package.metadata.example.transform_curve_animation]
name = "Transform Curve Animation"
description = "Direct animation of an entity's Transform using curves"
category = "Animation"
wasm = true

# Application
[[example]]
name = "custom_loop"
Expand Down
150 changes: 150 additions & 0 deletions crates/bevy_animation/src/animatable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use bevy_color::{Laba, LinearRgba, Oklaba, Srgba, Xyza};
use bevy_math::*;
use bevy_reflect::Reflect;
use bevy_transform::prelude::Transform;
use bevy_utils::default;

/// An individual input for [`Animatable::blend`].
pub struct BlendInput<T> {
Expand Down Expand Up @@ -198,6 +199,124 @@ impl Animatable for Quat {
}
}

/// Basically `Transform`, but with every part optional. Note that this is the true
/// output of `Transform` animation, since individual parts may be unconstrained
/// if they lack an animation curve to control them.
#[derive(Default, Debug, Clone, Reflect)]
pub(crate) struct TransformParts {
pub(crate) translation: Option<Vec3>,
pub(crate) rotation: Option<Quat>,
pub(crate) scale: Option<Vec3>,
}

impl TransformParts {
pub(crate) fn apply_to_transform(self, transform: &mut Transform) {
if let Some(translation) = self.translation {
transform.translation = translation;
}
if let Some(rotation) = self.rotation {
transform.rotation = rotation;
}
if let Some(scale) = self.scale {
transform.scale = scale;
}
}

#[inline]
pub(crate) fn from_translation(translation: Vec3) -> Self {
Self {
translation: Some(translation),
..default()
}
}

#[inline]
pub(crate) fn from_rotation(rotation: Quat) -> Self {
Self {
rotation: Some(rotation),
..default()
}
}

#[inline]
pub(crate) fn from_scale(scale: Vec3) -> Self {
Self {
scale: Some(scale),
..default()
}
}

#[inline]
pub(crate) fn from_transform(transform: Transform) -> Self {
Self {
translation: Some(transform.translation),
rotation: Some(transform.rotation),
scale: Some(transform.scale),
}
}
}

fn interpolate_option<A: Animatable + Copy>(a: Option<&A>, b: Option<&A>, time: f32) -> Option<A> {
match (a, b) {
(None, None) => None,
(Some(a), None) => Some(*a),
(None, Some(b)) => Some(*b),
(Some(a), Some(b)) => Some(A::interpolate(a, b, time)),
}
}

impl Animatable for TransformParts {
fn interpolate(a: &Self, b: &Self, time: f32) -> Self {
TransformParts {
translation: interpolate_option(a.translation.as_ref(), b.translation.as_ref(), time),
rotation: interpolate_option(a.rotation.as_ref(), b.rotation.as_ref(), time),
scale: interpolate_option(a.scale.as_ref(), b.scale.as_ref(), time),
}
}

fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
let mut accum = TransformParts::default();
for BlendInput {
weight,
value,
additive,
} in inputs
{
if additive {
let Self {
translation,
rotation,
scale,
} = value;
if let Some(translation) = translation {
let weighted_translation = translation * weight;
match accum.translation {
Some(ref mut v) => *v += weighted_translation,
None => accum.translation = Some(weighted_translation),
}
}
if let Some(rotation) = rotation {
let weighted_rotation = Quat::slerp(Quat::IDENTITY, rotation, weight);
match accum.rotation {
Some(ref mut r) => *r = weighted_rotation * *r,
None => accum.rotation = Some(weighted_rotation),
}
}
if let Some(scale) = scale {
let weighted_scale = scale * weight;
match accum.scale {
Some(ref mut s) => *s += weighted_scale,
None => accum.scale = Some(weighted_scale),
}
}
} else {
accum = Self::interpolate(&accum, &value, weight);
}
}
accum
}
}

/// Evaluates a cubic Bézier curve at a value `t`, given two endpoints and the
/// derivatives at those endpoints.
///
Expand Down Expand Up @@ -271,3 +390,34 @@ where
let p1p2p3 = T::interpolate(&p1p2, &p2p3, t);
T::interpolate(&p0p1p2, &p1p2p3, t)
}

#[cfg(test)]
mod tests {
use super::Animatable;
use super::TransformParts;
use crate::prelude::BlendInput;
use bevy_math::vec3;
use bevy_transform::components::Transform;

#[test]
fn add_parts() {
let parts = TransformParts::from_transform(Transform::from_xyz(1.0, 2.0, 3.0));
let incoming = TransformParts::from_translation(vec3(1.0, 1.0, 1.0));
let blend = TransformParts::blend(
[
BlendInput {
weight: 1.0,
value: parts,
additive: true,
},
BlendInput {
weight: 0.1,
value: incoming,
additive: true,
},
]
.into_iter(),
);
println!("Blend: {:?}", blend);
}
}
Loading