Path: blob/main/crates/bevy_animation/src/animatable.rs
6595 views
//! Traits and type for interpolating between values.12use crate::util;3use bevy_color::{Laba, LinearRgba, Oklaba, Srgba, Xyza};4use bevy_math::*;5use bevy_reflect::Reflect;6use bevy_transform::prelude::Transform;78/// An individual input for [`Animatable::blend`].9pub struct BlendInput<T> {10/// The individual item's weight. This may not be bound to the range `[0.0, 1.0]`.11pub weight: f32,12/// The input value to be blended.13pub value: T,14/// Whether or not to additively blend this input into the final result.15pub additive: bool,16}1718/// An animatable value type.19pub trait Animatable: Reflect + Sized + Send + Sync + 'static {20/// Interpolates between `a` and `b` with an interpolation factor of `time`.21///22/// The `time` parameter here may not be clamped to the range `[0.0, 1.0]`.23fn interpolate(a: &Self, b: &Self, time: f32) -> Self;2425/// Blends one or more values together.26///27/// Implementors should return a default value when no inputs are provided here.28fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self;29}3031macro_rules! impl_float_animatable {32($ty: ty, $base: ty) => {33impl Animatable for $ty {34#[inline]35fn interpolate(a: &Self, b: &Self, t: f32) -> Self {36let t = <$base>::from(t);37(*a) * (1.0 - t) + (*b) * t38}3940#[inline]41fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {42let mut value = Default::default();43for input in inputs {44if input.additive {45value += <$base>::from(input.weight) * input.value;46} else {47value = Self::interpolate(&value, &input.value, input.weight);48}49}50value51}52}53};54}5556macro_rules! impl_color_animatable {57($ty: ident) => {58impl Animatable for $ty {59#[inline]60fn interpolate(a: &Self, b: &Self, t: f32) -> Self {61let value = *a * (1. - t) + *b * t;62value63}6465#[inline]66fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {67let mut value = Default::default();68for input in inputs {69if input.additive {70value += input.weight * input.value;71} else {72value = Self::interpolate(&value, &input.value, input.weight);73}74}75value76}77}78};79}8081impl_float_animatable!(f32, f32);82impl_float_animatable!(Vec2, f32);83impl_float_animatable!(Vec3A, f32);84impl_float_animatable!(Vec4, f32);8586impl_float_animatable!(f64, f64);87impl_float_animatable!(DVec2, f64);88impl_float_animatable!(DVec3, f64);89impl_float_animatable!(DVec4, f64);9091impl_color_animatable!(LinearRgba);92impl_color_animatable!(Laba);93impl_color_animatable!(Oklaba);94impl_color_animatable!(Srgba);95impl_color_animatable!(Xyza);9697// Vec3 is special cased to use Vec3A internally for blending98impl Animatable for Vec3 {99#[inline]100fn interpolate(a: &Self, b: &Self, t: f32) -> Self {101(*a) * (1.0 - t) + (*b) * t102}103104#[inline]105fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {106let mut value = Vec3A::ZERO;107for input in inputs {108if input.additive {109value += input.weight * Vec3A::from(input.value);110} else {111value = Vec3A::interpolate(&value, &Vec3A::from(input.value), input.weight);112}113}114Self::from(value)115}116}117118impl Animatable for bool {119#[inline]120fn interpolate(a: &Self, b: &Self, t: f32) -> Self {121util::step_unclamped(*a, *b, t)122}123124#[inline]125fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {126inputs127.max_by_key(|x| FloatOrd(x.weight))128.is_some_and(|input| input.value)129}130}131132impl Animatable for Transform {133fn interpolate(a: &Self, b: &Self, t: f32) -> Self {134Self {135translation: Vec3::interpolate(&a.translation, &b.translation, t),136rotation: Quat::interpolate(&a.rotation, &b.rotation, t),137scale: Vec3::interpolate(&a.scale, &b.scale, t),138}139}140141fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {142let mut translation = Vec3A::ZERO;143let mut scale = Vec3A::ZERO;144let mut rotation = Quat::IDENTITY;145146for input in inputs {147if input.additive {148translation += input.weight * Vec3A::from(input.value.translation);149scale += input.weight * Vec3A::from(input.value.scale);150rotation =151Quat::slerp(Quat::IDENTITY, input.value.rotation, input.weight) * rotation;152} else {153translation = Vec3A::interpolate(154&translation,155&Vec3A::from(input.value.translation),156input.weight,157);158scale = Vec3A::interpolate(&scale, &Vec3A::from(input.value.scale), input.weight);159rotation = Quat::interpolate(&rotation, &input.value.rotation, input.weight);160}161}162163Self {164translation: Vec3::from(translation),165rotation,166scale: Vec3::from(scale),167}168}169}170171impl Animatable for Quat {172/// Performs a slerp to smoothly interpolate between quaternions.173#[inline]174fn interpolate(a: &Self, b: &Self, t: f32) -> Self {175// We want to smoothly interpolate between the two quaternions by default,176// rather than using a quicker but less correct linear interpolation.177a.slerp(*b, t)178}179180#[inline]181fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {182let mut value = Self::IDENTITY;183for BlendInput {184weight,185value: incoming_value,186additive,187} in inputs188{189if additive {190value = Self::slerp(Self::IDENTITY, incoming_value, weight) * value;191} else {192value = Self::interpolate(&value, &incoming_value, weight);193}194}195value196}197}198199/// Evaluates a cubic Bézier curve at a value `t`, given two endpoints and the200/// derivatives at those endpoints.201///202/// The derivatives are linearly scaled by `duration`.203pub fn interpolate_with_cubic_bezier<T>(p0: &T, d0: &T, d3: &T, p3: &T, t: f32, duration: f32) -> T204where205T: Animatable + Clone,206{207// We're given two endpoints, along with the derivatives at those endpoints,208// and have to evaluate the cubic Bézier curve at time t using only209// (additive) blending and linear interpolation.210//211// Evaluating a Bézier curve via repeated linear interpolation when the212// control points are known is straightforward via [de Casteljau213// subdivision]. So the only remaining problem is to get the two off-curve214// control points. The [derivative of the cubic Bézier curve] is:215//216// B′(t) = 3(1 - t)²(P₁ - P₀) + 6(1 - t)t(P₂ - P₁) + 3t²(P₃ - P₂)217//218// Setting t = 0 and t = 1 and solving gives us:219//220// P₁ = P₀ + B′(0) / 3221// P₂ = P₃ - B′(1) / 3222//223// These P₁ and P₂ formulas can be expressed as additive blends.224//225// So, to sum up, first we calculate the off-curve control points via226// additive blending, and then we use repeated linear interpolation to227// evaluate the curve.228//229// [de Casteljau subdivision]: https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm230// [derivative of the cubic Bézier curve]: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B%C3%A9zier_curves231232// Compute control points from derivatives.233let p1 = T::blend(234[235BlendInput {236weight: duration / 3.0,237value: (*d0).clone(),238additive: true,239},240BlendInput {241weight: 1.0,242value: (*p0).clone(),243additive: true,244},245]246.into_iter(),247);248let p2 = T::blend(249[250BlendInput {251weight: duration / -3.0,252value: (*d3).clone(),253additive: true,254},255BlendInput {256weight: 1.0,257value: (*p3).clone(),258additive: true,259},260]261.into_iter(),262);263264// Use de Casteljau subdivision to evaluate.265let p0p1 = T::interpolate(p0, &p1, t);266let p1p2 = T::interpolate(&p1, &p2, t);267let p2p3 = T::interpolate(&p2, p3, t);268let p0p1p2 = T::interpolate(&p0p1, &p1p2, t);269let p1p2p3 = T::interpolate(&p1p2, &p2p3, t);270T::interpolate(&p0p1p2, &p1p2p3, t)271}272273274