From fd30d0404d54677a3f55d87020fb7c7a20bcd345 Mon Sep 17 00:00:00 2001 From: Matthew Weatherley Date: Fri, 31 May 2024 15:33:55 -0400 Subject: [PATCH] Refactor out Interpolate from Animatable, make example --- Cargo.toml | 11 ++ crates/bevy_animation/src/animatable.rs | 46 +----- crates/bevy_animation/src/lib.rs | 1 - crates/bevy_animation/src/util.rs | 10 -- crates/bevy_math/src/common_traits.rs | 48 +++++- crates/bevy_math/src/lib.rs | 6 +- .../src/components/transform.rs | 12 +- examples/README.md | 1 + examples/math/smooth_follow.rs | 145 ++++++++++++++++++ 9 files changed, 218 insertions(+), 62 deletions(-) delete mode 100644 crates/bevy_animation/src/util.rs create mode 100644 examples/math/smooth_follow.rs diff --git a/Cargo.toml b/Cargo.toml index fae29567b7bb4..00989b1fd2304 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2993,6 +2993,17 @@ description = "Demonstrates how to sample random points from mathematical primit category = "Math" wasm = true +[[example]] +name = "smooth_follow" +path = "examples/math/smooth_follow.rs" +doc-scrape-examples = true + +[package.metadata.example.smooth_follow] +name = "Smooth Follow" +description = "Demonstrates how to make an entity smoothly follow another using interpolation" +category = "Math" +wasm = true + # Gizmos [[example]] name = "2d_gizmos" diff --git a/crates/bevy_animation/src/animatable.rs b/crates/bevy_animation/src/animatable.rs index 4e59ccc8b2875..1e09aa70380c4 100644 --- a/crates/bevy_animation/src/animatable.rs +++ b/crates/bevy_animation/src/animatable.rs @@ -1,4 +1,3 @@ -use crate::util; use bevy_color::{Laba, LinearRgba, Oklaba, Srgba, Xyza}; use bevy_ecs::world::World; use bevy_math::*; @@ -16,12 +15,7 @@ pub struct BlendInput { } /// An animatable value type. -pub trait Animatable: Reflect + Sized + Send + Sync + 'static { - /// Interpolates between `a` and `b` with a interpolation factor of `time`. - /// - /// The `time` parameter here may not be clamped to the range `[0.0, 1.0]`. - fn interpolate(a: &Self, b: &Self, time: f32) -> Self; - +pub trait Animatable: Reflect + Interpolate + Sized + Send + Sync + 'static { /// Blends one or more values together. /// /// Implementors should return a default value when no inputs are provided here. @@ -35,12 +29,6 @@ pub trait Animatable: Reflect + Sized + Send + Sync + 'static { macro_rules! impl_float_animatable { ($ty: ty, $base: ty) => { impl Animatable for $ty { - #[inline] - fn interpolate(a: &Self, b: &Self, t: f32) -> Self { - let t = <$base>::from(t); - (*a) * (1.0 - t) + (*b) * t - } - #[inline] fn blend(inputs: impl Iterator>) -> Self { let mut value = Default::default(); @@ -60,12 +48,6 @@ macro_rules! impl_float_animatable { macro_rules! impl_color_animatable { ($ty: ident) => { impl Animatable for $ty { - #[inline] - fn interpolate(a: &Self, b: &Self, t: f32) -> Self { - let value = *a * (1. - t) + *b * t; - value - } - #[inline] fn blend(inputs: impl Iterator>) -> Self { let mut value = Default::default(); @@ -100,11 +82,6 @@ impl_color_animatable!(Xyza); // Vec3 is special cased to use Vec3A internally for blending impl Animatable for Vec3 { - #[inline] - fn interpolate(a: &Self, b: &Self, t: f32) -> Self { - (*a) * (1.0 - t) + (*b) * t - } - #[inline] fn blend(inputs: impl Iterator>) -> Self { let mut value = Vec3A::ZERO; @@ -120,11 +97,6 @@ impl Animatable for Vec3 { } impl Animatable for bool { - #[inline] - fn interpolate(a: &Self, b: &Self, t: f32) -> Self { - util::step_unclamped(*a, *b, t) - } - #[inline] fn blend(inputs: impl Iterator>) -> Self { inputs @@ -135,14 +107,6 @@ impl Animatable for bool { } impl Animatable for Transform { - fn interpolate(a: &Self, b: &Self, t: f32) -> Self { - Self { - translation: Vec3::interpolate(&a.translation, &b.translation, t), - rotation: Quat::interpolate(&a.rotation, &b.rotation, t), - scale: Vec3::interpolate(&a.scale, &b.scale, t), - } - } - fn blend(inputs: impl Iterator>) -> Self { let mut translation = Vec3A::ZERO; let mut scale = Vec3A::ZERO; @@ -173,14 +137,6 @@ impl Animatable for Transform { } impl Animatable for Quat { - /// Performs a slerp to smoothly interpolate between quaternions. - #[inline] - fn interpolate(a: &Self, b: &Self, t: f32) -> Self { - // We want to smoothly interpolate between the two quaternions by default, - // rather than using a quicker but less correct linear interpolation. - a.slerp(*b, t) - } - #[inline] fn blend(inputs: impl Iterator>) -> Self { let mut value = Self::IDENTITY; diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index f634d13259159..7bf62a32de2a1 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -10,7 +10,6 @@ mod animatable; mod graph; mod transition; -mod util; use std::cell::RefCell; use std::collections::BTreeMap; diff --git a/crates/bevy_animation/src/util.rs b/crates/bevy_animation/src/util.rs deleted file mode 100644 index 67aaf8116e365..0000000000000 --- a/crates/bevy_animation/src/util.rs +++ /dev/null @@ -1,10 +0,0 @@ -/// Steps between two different discrete values of any type. -/// Returns `a` if `t < 1.0`, otherwise returns `b`. -#[inline] -pub(crate) fn step_unclamped(a: T, b: T, t: f32) -> T { - if t < 1.0 { - a - } else { - b - } -} diff --git a/crates/bevy_math/src/common_traits.rs b/crates/bevy_math/src/common_traits.rs index 59308394c6118..a7d06ae6cee23 100644 --- a/crates/bevy_math/src/common_traits.rs +++ b/crates/bevy_math/src/common_traits.rs @@ -1,4 +1,4 @@ -use crate::{Dir2, Dir3, Dir3A, Quat, Vec2, Vec3, Vec3A, Vec4}; +use crate::{DVec2, DVec3, DVec4, Dir2, Dir3, Dir3A, Quat, Vec2, Vec3, Vec3A, Vec4}; use std::fmt::Debug; use std::ops::{Add, Div, Mul, Neg, Sub}; @@ -216,35 +216,79 @@ pub trait Interpolate: Clone { } } +/// Steps between two different discrete values of any type. +/// Returns `a` if `t < 1.0`, otherwise returns `b`. +/// +/// This is a common form of interpolation for discrete types. +#[inline] +fn step_unclamped(a: T, b: T, t: f32) -> T { + if t < 1.0 { + a + } else { + b + } +} + impl Interpolate for V where V: VectorSpace, { + #[inline] fn interpolate(&self, other: &Self, t: f32) -> Self { - *self * (1.0 - t) + *other * t + self.lerp(*other, t) } } impl Interpolate for Quat { + #[inline] fn interpolate(&self, other: &Self, t: f32) -> Self { self.slerp(*other, t) } } impl Interpolate for Dir2 { + #[inline] fn interpolate(&self, other: &Self, t: f32) -> Self { self.slerp(*other, t) } } impl Interpolate for Dir3 { + #[inline] fn interpolate(&self, other: &Self, t: f32) -> Self { self.slerp(*other, t) } } impl Interpolate for Dir3A { + #[inline] fn interpolate(&self, other: &Self, t: f32) -> Self { self.slerp(*other, t) } } + +/// This macro is for implementing `Interpolate` on non-f32-based vector-space-like entities. +macro_rules! impl_float_interpolate { + ($ty: ty, $base: ty) => { + impl Interpolate for $ty { + #[inline] + fn interpolate(&self, other: &Self, t: f32) -> Self { + let t = <$base>::from(t); + (*self) * (1.0 - t) + (*other) * t + } + } + }; +} + +impl_float_interpolate!(f64, f64); +impl_float_interpolate!(DVec2, f64); +impl_float_interpolate!(DVec3, f64); +impl_float_interpolate!(DVec4, f64); + +// This is slightly cursed but necessary for unifying with an `Animatable` implementation for `bool` +impl Interpolate for bool { + #[inline] + fn interpolate(&self, other: &Self, t: f32) -> Self { + step_unclamped(*self, *other, t) + } +} diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index c98c328d1befa..cdb4e01fa0b9e 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -50,9 +50,9 @@ pub mod prelude { }, direction::{Dir2, Dir3, Dir3A}, primitives::*, - BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, - Quat, Ray2d, Ray3d, Rect, Rotation2d, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, - Vec3Swizzles, Vec4, Vec4Swizzles, + BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Interpolate, Mat2, + Mat3, Mat4, Quat, Ray2d, Ray3d, Rect, Rotation2d, URect, UVec2, UVec3, UVec4, Vec2, + Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, }; } diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index a7c3d4db0c397..2af438f4fb2b5 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -1,6 +1,6 @@ use super::GlobalTransform; use bevy_ecs::{component::Component, reflect::ReflectComponent}; -use bevy_math::{Affine3A, Dir3, Mat3, Mat4, Quat, Vec3}; +use bevy_math::{Affine3A, Dir3, Interpolate, Mat3, Mat4, Quat, Vec3}; use bevy_reflect::prelude::*; use bevy_reflect::Reflect; use std::ops::Mul; @@ -559,3 +559,13 @@ impl Mul for Transform { self.transform_point(value) } } + +impl Interpolate for Transform { + fn interpolate(&self, other: &Self, t: f32) -> Self { + Transform { + translation: self.translation.interpolate(&other.translation, t), + rotation: self.rotation.interpolate(&other.rotation, t), + scale: self.scale.interpolate(&other.scale, t), + } + } +} diff --git a/examples/README.md b/examples/README.md index 661c9c6a78d0c..aaf3b0e6dc32f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -323,6 +323,7 @@ Example | Description [Random Sampling](../examples/math/random_sampling.rs) | Demonstrates how to sample random points from mathematical primitives [Rendering Primitives](../examples/math/render_primitives.rs) | Shows off rendering for all math primitives as both Meshes and Gizmos [Sampling Primitives](../examples/math/sampling_primitives.rs) | Demonstrates all the primitives which can be sampled. +[Smooth Follow](../examples/math/smooth_follow.rs) | Demonstrates how to make an entity smoothly follow another using interpolation ## Reflection diff --git a/examples/math/smooth_follow.rs b/examples/math/smooth_follow.rs new file mode 100644 index 0000000000000..f15fd65fa0d17 --- /dev/null +++ b/examples/math/smooth_follow.rs @@ -0,0 +1,145 @@ +//! This example demonstrates how to use interpolation to make one entity smoothly follow another. + +use bevy::math::{prelude::*, vec3, NormedVectorSpace}; +use bevy::prelude::*; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; +use std::cmp::min_by; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, (move_target, move_follower).chain()) + .run(); +} + +// The sphere that the following sphere targets at all times: +#[derive(Component)] +struct TargetSphere; + +// The speed of the target sphere moving to its next location: +#[derive(Resource)] +struct TargetSphereSpeed(f32); + +// The position that the target sphere always moves linearly toward: +#[derive(Resource)] +struct TargetPosition(Vec3); + +// The decay rate used by the smooth following: +#[derive(Resource)] +struct DecayRate(f32); + +// The sphere that follows the target sphere by moving towards it with nudging: +#[derive(Component)] +struct FollowingSphere; + +/// The source of randomness used by this example. +#[derive(Resource)] +struct RandomSource(ChaCha8Rng); + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // A plane: + commands.spawn(PbrBundle { + mesh: meshes.add(Plane3d::default().mesh().size(12.0, 12.0)), + material: materials.add(Color::srgb(0.3, 0.15, 0.3)), + transform: Transform::from_xyz(0.0, -2.5, 0.0), + ..default() + }); + + // The target sphere: + commands.spawn(( + PbrBundle { + mesh: meshes.add(Sphere::new(0.3)), + material: materials.add(Color::srgb(0.3, 0.15, 0.9)), + ..default() + }, + TargetSphere, + )); + + // The sphere that follows it: + commands.spawn(( + PbrBundle { + mesh: meshes.add(Sphere::new(0.3)), + material: materials.add(Color::srgb(0.9, 0.3, 0.3)), + transform: Transform::from_translation(vec3(0.0, -2.0, 0.0)), + ..default() + }, + FollowingSphere, + )); + + // A light: + commands.spawn(PointLightBundle { + point_light: PointLight { + intensity: 15_000_000.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + + // A camera: + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // Set starting values for resources used by the systems: + commands.insert_resource(TargetSphereSpeed(5.0)); + commands.insert_resource(DecayRate(2.0)); + commands.insert_resource(TargetPosition(Vec3::ZERO)); + commands.insert_resource(RandomSource(ChaCha8Rng::seed_from_u64(68941654987813521))); +} + +fn move_target( + mut target: Query<&mut Transform, With>, + target_speed: Res, + mut target_pos: ResMut, + time: Res