diff --git a/Cargo.toml b/Cargo.toml index 3b60e1dff47b7..dc13e0cf20c03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -351,4 +351,3 @@ icon = "@mipmap/ic_launcher" build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"] min_sdk_version = 16 target_sdk_version = 29 - diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index a5e0705cee747..e119a62752378 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -21,4 +21,4 @@ bevy_transform = { path = "../bevy_transform", version = "0.3.0" } bevy_render = { path = "../bevy_render", version = "0.3.0" } bevy_property = { path = "../bevy_property", version = "0.3.0" } -splines = { version = "3.5.0", features = ["serialization"] } \ No newline at end of file +splines = { version = "3.5.0", features = ["serialization", "impl-glam"] } diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index dfc247f720d37..9b1315963574d 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -15,8 +15,9 @@ pub mod prelude { plugin::AnimationPlugin, spline_group::{LoopStyle, SplineGroup}, spline_groups::{ - one::AnimationSplineOne, three::AnimationSplineThree, - transform::AnimationSplineTransform, + one::AnimationSplineOne, + three::AnimationSplineThree, + transform::{AnimationSplineTransform, SplineQuatExt}, }, vec3_option::Vec3Option, }; diff --git a/crates/bevy_animation/src/plugin.rs b/crates/bevy_animation/src/plugin.rs index 4b997a474c1a5..f14097eff7b01 100644 --- a/crates/bevy_animation/src/plugin.rs +++ b/crates/bevy_animation/src/plugin.rs @@ -8,7 +8,7 @@ use crate::{ use bevy_app::{AppBuilder, Plugin}; use bevy_core::Time; use bevy_ecs::{IntoSystem, Query, Res}; -use bevy_math::{Quat, Vec3}; +use bevy_math::Vec3; use bevy_transform::components::Transform; #[derive(Default)] @@ -41,15 +41,15 @@ fn advance_animation_transform( for (mut transform, mut splines) in q.iter_mut() { let mut scale = transform.scale; splines.advance(time.delta_seconds); - let s = splines.current(); - s.translation.alter(&mut transform.translation); - if let Some(sample_scale) = s.scale { + let sample = splines.current(); + sample.translation.alter(&mut transform.translation); + if let Some(sample_scale) = sample.scale { scale = Vec3::one() * sample_scale; } - let mut rot = Vec3::zero(); - s.rotation.alter(&mut rot); *transform = Transform::from_translation(transform.translation); - transform.rotation = Quat::from_rotation_ypr(rot.x, rot.y, rot.z); + if let Some(rotation) = sample.rotation { + transform.rotation = rotation; + } transform.apply_non_uniform_scale(scale); } } diff --git a/crates/bevy_animation/src/spline_group.rs b/crates/bevy_animation/src/spline_group.rs index 9780d0b9d75c1..279ba04a6385f 100644 --- a/crates/bevy_animation/src/spline_group.rs +++ b/crates/bevy_animation/src/spline_group.rs @@ -1,6 +1,21 @@ use core::time::Duration; use splines::Spline; +pub(crate) trait SplineExt { + fn start_time(&self) -> Option; + fn end_time(&self) -> Option; +} + +impl SplineExt for Spline { + fn start_time(&self) -> Option { + self.keys().first().map(|k| k.t) + } + + fn end_time(&self) -> Option { + self.keys().last().map(|k| k.t) + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum LoopStyle { Once, @@ -11,8 +26,6 @@ pub enum LoopStyle { pub trait SplineGroup { type Sample; - fn splines(&self) -> Vec<&Spline>; - fn loop_style(&self) -> LoopStyle; fn loop_style_mut(&mut self) -> &mut LoopStyle; @@ -34,46 +47,11 @@ pub trait SplineGroup { self.sample(self.time()) } - fn is_empty(&self) -> bool { - let any_not_empty = self.splines().into_iter().any(|v| !v.is_empty()); - !any_not_empty - } + fn is_empty(&self) -> bool; - fn start_time(&self) -> Option { - let starts: Vec = self - .splines() - .into_iter() - .filter_map(spline_start_time) - .collect(); - - if starts.is_empty() { - None - } else { - Some( - starts - .iter() - .fold(starts[0], |acc, v| if *v < acc { *v } else { acc }), - ) - } - } + fn start_time(&self) -> Option; - fn end_time(&self) -> Option { - let ends: Vec = self - .splines() - .into_iter() - .map(|s| spline_end_time(s)) - .filter_map(|s| s) - .collect(); - - if ends.is_empty() { - None - } else { - Some( - ends.iter() - .fold(ends[0], |acc, v| if *v > acc { *v } else { acc }), - ) - } - } + fn end_time(&self) -> Option; fn duration(&self) -> Option { self.start_time() @@ -147,13 +125,3 @@ pub trait SplineGroup { *self.paused_mut() = !paused; } } - -fn spline_start_time(spline: &Spline) -> Option { - spline.get(0).map(|first_key| first_key.t) -} - -fn spline_end_time(spline: &Spline) -> Option { - spline - .get(spline.len().saturating_sub(1)) - .map(|last_key| last_key.t) -} diff --git a/crates/bevy_animation/src/spline_groups/one.rs b/crates/bevy_animation/src/spline_groups/one.rs index f38ced8bb64ed..c514026863f18 100644 --- a/crates/bevy_animation/src/spline_groups/one.rs +++ b/crates/bevy_animation/src/spline_groups/one.rs @@ -1,4 +1,4 @@ -use crate::spline_group::{LoopStyle, SplineGroup}; +use crate::spline_group::{LoopStyle, SplineExt, SplineGroup}; use splines::Spline; pub struct AnimationSplineOne { @@ -26,8 +26,16 @@ impl Default for AnimationSplineOne { impl SplineGroup for AnimationSplineOne { type Sample = Option; - fn splines(&self) -> Vec<&Spline> { - vec![&self.spline] + fn is_empty(&self) -> bool { + self.spline.is_empty() + } + + fn start_time(&self) -> Option { + self.spline.start_time() + } + + fn end_time(&self) -> Option { + self.spline.end_time() } fn loop_style(&self) -> LoopStyle { diff --git a/crates/bevy_animation/src/spline_groups/three.rs b/crates/bevy_animation/src/spline_groups/three.rs index c63b90e5718f1..9ddc7f8bd948f 100644 --- a/crates/bevy_animation/src/spline_groups/three.rs +++ b/crates/bevy_animation/src/spline_groups/three.rs @@ -1,5 +1,5 @@ use crate::{ - spline_group::{LoopStyle, SplineGroup}, + spline_group::{LoopStyle, SplineExt, SplineGroup}, vec3_option::Vec3Option, }; use splines::Spline; @@ -31,11 +31,33 @@ impl Default for AnimationSplineThree { } } +fn cmp_f32(a: &f32, b: &f32) -> std::cmp::Ordering { + a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) +} + impl SplineGroup for AnimationSplineThree { type Sample = Vec3Option; - fn splines(&self) -> Vec<&Spline> { - vec![&self.x, &self.y, &self.z] + fn is_empty(&self) -> bool { + self.x.is_empty() && self.y.is_empty() && self.z.is_empty() + } + + fn start_time(&self) -> Option { + self.x + .start_time() + .into_iter() + .chain(self.y.start_time()) + .chain(self.z.start_time()) + .min_by(cmp_f32) + } + + fn end_time(&self) -> Option { + self.x + .end_time() + .into_iter() + .chain(self.y.end_time()) + .chain(self.z.end_time()) + .max_by(cmp_f32) } fn loop_style(&self) -> LoopStyle { diff --git a/crates/bevy_animation/src/spline_groups/transform.rs b/crates/bevy_animation/src/spline_groups/transform.rs index c363cb704052f..c748f108bf250 100644 --- a/crates/bevy_animation/src/spline_groups/transform.rs +++ b/crates/bevy_animation/src/spline_groups/transform.rs @@ -1,12 +1,181 @@ use crate::{ - spline_group::{LoopStyle, SplineGroup}, + spline_group::{LoopStyle, SplineExt, SplineGroup}, vec3_option::Vec3Option, }; -use splines::Spline; +use bevy_math::Quat; +use splines::{Interpolation, Key, Spline}; + +/// A wrapper for a `Quat`, which represents the orientation of an animation keyframe. +#[derive(Clone, Copy)] +#[repr(transparent)] +struct SlerpWrapper(Quat); + +impl std::ops::Add for SlerpWrapper { + type Output = Self; + + fn add(self, other: Self) -> Self { + #[allow(clippy::suspicious_arithmetic_impl)] + Self(self.0 * other.0) + } +} + +impl std::ops::Sub for SlerpWrapper { + type Output = Self; + + fn sub(self, other: Self) -> Self { + #[allow(clippy::suspicious_arithmetic_impl)] + Self(self.0 * other.0.conjugate()) + } +} + +impl splines::interpolate::Linear for SlerpWrapper { + fn outer_mul(self, t: f32) -> Self { + Self(Quat::identity().slerp(self.0, t)) + } + + fn outer_div(self, t: f32) -> Self { + self.outer_mul(1.0 / t) + } +} + +impl splines::Interpolate for SlerpWrapper { + fn lerp(Self(a): Self, Self(b): Self, t: f32) -> Self { + Self(a.slerp(b, t)) + } + + fn quadratic_bezier(_: Self, _: Self, _: Self, _: f32) -> Self { + todo!() + } + + fn cubic_bezier(_: Self, _: Self, _: Self, _: Self, _: f32) -> Self { + todo!() + } +} + +impl Into for SlerpWrapper { + fn into(self) -> Quat { + self.0 + } +} + +impl AsRef for SlerpWrapper { + fn as_ref(&self) -> &Quat { + &self.0 + } +} + +impl AsMut for SlerpWrapper { + fn as_mut(&mut self) -> &mut Quat { + &mut self.0 + } +} + +pub trait SplineQuatExt { + fn slerp(self) -> Self; +} + +impl SplineQuatExt for Spline { + fn slerp(self) -> Self { + let keys = self.keys(); + + if keys.len() < 2 { + return self; + } + + let mut new_keys = Vec::with_capacity(keys.len()); + for window in keys.windows(2) { + // TODO array_windows + let [a, b] = match *window { + [a, b] => [a, b], + _ => unreachable!(), + }; + + match a.interpolation { + Interpolation::Step(_) => new_keys.push(a), + Interpolation::Linear => { + new_keys.push(a); + + const COS_MIN_ANGLE: f32 = 0.9995; + let cos_angle = a.value.dot(b.value); + if cos_angle < COS_MIN_ANGLE { + let angle = cos_angle.acos(); + let min_angle = COS_MIN_ANGLE.acos(); + let steps = (angle / min_angle) as u32 + 1; + + let step_t = (b.t - a.t) / steps as f32; + for i in 1..steps { + let delta_t = (i as f32) * step_t; + new_keys.push(Key { + value: a.value.slerp(b.value, delta_t), + t: delta_t + a.t, + interpolation: Interpolation::Linear, + }); + } + } + } + _ => unimplemented!("Only Interpolation::Linear and Step are supported for now."), + } + } + new_keys.push(*keys.last().unwrap()); + + Spline::from_vec(new_keys) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn noop_resample() { + const MIN_ANGLE: f32 = 0.0316; + let spline = Spline::from_vec(vec![ + Key { + t: 0., + value: Quat::from_axis_angle(bevy_math::Vec3::unit_z(), 0.), + interpolation: Interpolation::Linear, + }, + Key { + t: 1., + value: Quat::from_axis_angle(bevy_math::Vec3::unit_z(), MIN_ANGLE), + interpolation: Interpolation::Linear, + }, + Key { + t: 2., + value: Quat::from_axis_angle(bevy_math::Vec3::unit_z(), 2. * MIN_ANGLE), + interpolation: Interpolation::Linear, + }, + ]); + + assert_eq!(spline.clone().keys(), spline.slerp().keys()); + } + + #[test] + fn big_resample() { + let spline = Spline::from_vec(vec![ + Key { + t: 0., + value: Quat::from_xyzw(0., 0., 0., 1.), + interpolation: Interpolation::Linear, + }, + Key { + t: 1., + value: Quat::from_xyzw(0., 0., 1., 0.), + interpolation: Interpolation::Linear, + }, + ]); + + let slerped = spline.clone().slerp(); + + assert_eq!(spline.start_time(), slerped.start_time()); + assert_eq!(spline.end_time(), slerped.end_time()); + assert!(spline.keys().len() < slerped.keys().len()); + } +} pub struct TransformSample { pub translation: Vec3Option, - pub rotation: Vec3Option, + pub rotation: Option, pub scale: Option, } @@ -14,9 +183,7 @@ pub struct AnimationSplineTransform { pub translation_x: Spline, pub translation_y: Spline, pub translation_z: Spline, - pub rotation_x: Spline, - pub rotation_y: Spline, - pub rotation_z: Spline, + pub rotation: Spline, pub scale: Spline, pub loop_style: LoopStyle, pub time: f32, @@ -31,9 +198,7 @@ impl Default for AnimationSplineTransform { translation_x: Spline::from_vec(vec![]), translation_y: Spline::from_vec(vec![]), translation_z: Spline::from_vec(vec![]), - rotation_x: Spline::from_vec(vec![]), - rotation_y: Spline::from_vec(vec![]), - rotation_z: Spline::from_vec(vec![]), + rotation: Spline::from_vec(vec![]), scale: Spline::from_vec(vec![]), loop_style: LoopStyle::Once, time: 0.0, @@ -44,19 +209,41 @@ impl Default for AnimationSplineTransform { } } +fn cmp_f32(a: &f32, b: &f32) -> std::cmp::Ordering { + a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) +} + impl SplineGroup for AnimationSplineTransform { type Sample = TransformSample; - fn splines(&self) -> Vec<&Spline> { - vec![ - &self.translation_x, - &self.translation_y, - &self.translation_z, - &self.rotation_x, - &self.rotation_y, - &self.rotation_z, - &self.scale, - ] + fn is_empty(&self) -> bool { + self.scale.is_empty() + && self.translation_x.is_empty() + && self.translation_y.is_empty() + && self.translation_z.is_empty() + && self.rotation.is_empty() + } + + fn start_time(&self) -> Option { + self.scale + .start_time() + .into_iter() + .chain(self.translation_x.start_time()) + .chain(self.translation_y.start_time()) + .chain(self.translation_z.start_time()) + .chain(self.rotation.start_time()) + .min_by(cmp_f32) + } + + fn end_time(&self) -> Option { + self.scale + .end_time() + .into_iter() + .chain(self.translation_x.end_time()) + .chain(self.translation_y.end_time()) + .chain(self.translation_z.end_time()) + .chain(self.rotation.end_time()) + .max_by(cmp_f32) } fn loop_style(&self) -> LoopStyle { @@ -106,11 +293,7 @@ impl SplineGroup for AnimationSplineTransform { self.translation_y.sample(time), self.translation_z.sample(time), ), - rotation: Vec3Option::new( - self.rotation_x.sample(time), - self.rotation_y.sample(time), - self.rotation_z.sample(time), - ), + rotation: self.rotation.sample(time), scale: self.scale.sample(time), } } diff --git a/examples/animation/animation_2d.rs b/examples/animation/animation_2d.rs index f121db5e28b2e..60b4f5366a97a 100644 --- a/examples/animation/animation_2d.rs +++ b/examples/animation/animation_2d.rs @@ -33,6 +33,29 @@ fn setup( Key::new(3.0, 100.0, Interpolation::Linear), ]), translation_z: Spline::from_vec(vec![]), + rotation: Spline::from_vec(vec![ + Key::new( + 0.0, + Quat::from_rotation_ypr(0., 0., 720_f32.to_radians()), + Interpolation::Linear, + ), + Key::new( + 1.0, + Quat::from_rotation_ypr(0., 0., 480_f32.to_radians()), + Interpolation::Linear, + ), + Key::new( + 2.0, + Quat::from_rotation_ypr(0., 0., 240_f32.to_radians()), + Interpolation::Linear, + ), + Key::new( + 3.0, + Quat::from_rotation_ypr(0., 0., 0_f32.to_radians()), + Interpolation::Linear, + ), + ]) + .slerp(), scale: Spline::from_vec(vec![ Key::new(0.0, 1.0, Interpolation::Cosine), Key::new(0.5, 1.5, Interpolation::Cosine),