#![no_std]
#![expect(clippy::type_complexity)]
#![warn(missing_docs)]
pub mod extrapolation;
pub mod interpolation;
pub mod hermite;
pub mod prelude {
#[doc(inline)]
pub use crate::{
NoRotationEasing, NoScaleEasing, NoTransformEasing, NoTranslationEasing,
TransformEasingPlugin,
extrapolation::*,
hermite::{
RotationHermiteEasing, TransformHermiteEasing, TransformHermiteEasingPlugin,
TranslationHermiteEasing,
},
interpolation::*,
};
}
use core::marker::PhantomData;
#[allow(unused_imports)]
use extrapolation::*;
#[allow(unused_imports)]
use interpolation::*;
use bevy::{
ecs::{change_detection::Tick, query::QueryData, system::SystemChangeTick},
prelude::*,
};
#[derive(Debug, Default)]
pub struct TransformEasingPlugin;
impl Plugin for TransformEasingPlugin {
fn build(&self, app: &mut App) {
app.register_type::<(
TranslationEasingState,
RotationEasingState,
ScaleEasingState,
NoTranslationEasing,
NoRotationEasing,
NoScaleEasing,
)>();
app.init_resource::<LastEasingTick>();
app.configure_sets(
FixedFirst,
(
TransformEasingSystems::Reset,
TransformEasingSystems::UpdateStart,
)
.chain(),
);
app.configure_sets(FixedLast, TransformEasingSystems::UpdateEnd);
app.configure_sets(
RunFixedMainLoop,
(
TransformEasingSystems::Ease,
TransformEasingSystems::UpdateEasingTick,
)
.chain()
.in_set(RunFixedMainLoopSystems::AfterFixedMainLoop),
);
app.add_systems(
FixedFirst,
(
reset_translation_easing,
reset_rotation_easing,
reset_scale_easing,
)
.chain()
.in_set(TransformEasingSystems::Reset),
);
app.add_systems(
RunFixedMainLoop,
reset_easing_states_on_transform_change.before(TransformEasingSystems::Ease),
);
app.add_systems(
RunFixedMainLoop,
(ease_translation_lerp, ease_rotation_slerp, ease_scale_lerp)
.in_set(TransformEasingSystems::Ease),
);
app.add_systems(
RunFixedMainLoop,
update_last_easing_tick.in_set(TransformEasingSystems::UpdateEasingTick),
);
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TransformEasingSystems {
Reset,
UpdateStart,
UpdateEnd,
Ease,
UpdateEasingTick,
}
#[deprecated(since = "0.3.0", note = "Renamed to `TransformEasingSystems`")]
pub type TransformEasingSet = TransformEasingSystems;
#[derive(Resource, Clone, Copy, Debug, Default, Deref, DerefMut)]
pub struct LastEasingTick(Tick);
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
#[require(NoTranslationEasing, NoRotationEasing, NoScaleEasing)]
pub struct NoTransformEasing;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
pub struct NoTranslationEasing;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
pub struct NoRotationEasing;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
pub struct NoScaleEasing;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
pub struct NonlinearTranslationEasing;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, Default)]
pub struct NonlinearRotationEasing;
pub trait VelocitySource: QueryData + Send + Sync + 'static {
type Previous: Component;
type Current: Component;
fn previous(start: &Self::Previous) -> Vec3;
fn current(end: &Self::Current) -> Vec3;
}
trait VelocitySourceItem<V>
where
V: VelocitySource,
{
fn previous(start: &V::Previous) -> Vec3;
fn current(end: &V::Current) -> Vec3;
}
impl<V: VelocitySource> VelocitySourceItem<V> for V::Item<'_, '_> {
fn previous(start: &V::Previous) -> Vec3 {
V::previous(start)
}
fn current(end: &V::Current) -> Vec3 {
V::current(end)
}
}
#[derive(Component)]
#[doc(hidden)]
pub struct DummyComponent(PhantomData<()>);
impl VelocitySource for () {
type Previous = DummyComponent;
type Current = DummyComponent;
fn previous(_: &Self::Previous) -> Vec3 {
Vec3::ZERO
}
fn current(_: &Self::Current) -> Vec3 {
Vec3::ZERO
}
}
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, Default)]
pub struct TranslationEasingState {
pub start: Option<Vec3>,
pub end: Option<Vec3>,
}
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, Default)]
pub struct RotationEasingState {
pub start: Option<Quat>,
pub end: Option<Quat>,
}
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, Default)]
pub struct ScaleEasingState {
pub start: Option<Vec3>,
pub end: Option<Vec3>,
}
fn update_last_easing_tick(
mut last_easing_tick: ResMut<LastEasingTick>,
system_change_tick: SystemChangeTick,
) {
*last_easing_tick = LastEasingTick(system_change_tick.this_run());
}
#[allow(clippy::type_complexity, private_interfaces)]
pub fn reset_easing_states_on_transform_change(
mut query: Query<
(
Ref<Transform>,
Option<&mut TranslationEasingState>,
Option<&mut RotationEasingState>,
Option<&mut ScaleEasingState>,
),
(
Changed<Transform>,
Or<(
With<TranslationEasingState>,
With<RotationEasingState>,
With<ScaleEasingState>,
)>,
),
>,
last_easing_tick: Res<LastEasingTick>,
system_change_tick: SystemChangeTick,
) {
let this_run = system_change_tick.this_run();
query.par_iter_mut().for_each(
|(transform, translation_easing, rotation_easing, scale_easing)| {
let last_changed = transform.last_changed();
let is_user_change = last_changed.is_newer_than(last_easing_tick.0, this_run);
if !is_user_change {
return;
}
if let Some(mut translation_easing) = translation_easing
&& let (Some(start), Some(end)) = (translation_easing.start, translation_easing.end)
&& transform.translation != start
&& transform.translation != end
{
translation_easing.start = None;
translation_easing.end = None;
}
if let Some(mut rotation_easing) = rotation_easing
&& let (Some(start), Some(end)) = (rotation_easing.start, rotation_easing.end)
&& transform.rotation != start
&& transform.rotation != end
{
rotation_easing.start = None;
rotation_easing.end = None;
}
if let Some(mut scale_easing) = scale_easing
&& let (Some(start), Some(end)) = (scale_easing.start, scale_easing.end)
&& transform.scale != start
&& transform.scale != end
{
scale_easing.start = None;
scale_easing.end = None;
}
},
);
}
fn reset_translation_easing(mut query: Query<&mut TranslationEasingState>) {
for mut easing in &mut query {
easing.start = None;
easing.end = None;
}
}
fn reset_rotation_easing(mut query: Query<&mut RotationEasingState>) {
for mut easing in &mut query {
easing.start = None;
easing.end = None;
}
}
fn reset_scale_easing(mut query: Query<&mut ScaleEasingState>) {
for mut easing in &mut query {
easing.start = None;
easing.end = None;
}
}
fn ease_translation_lerp(
mut query: Query<
(&mut Transform, &TranslationEasingState),
(
Without<NonlinearTranslationEasing>,
Without<NoTranslationEasing>,
),
>,
time: Res<Time<Fixed>>,
) {
let overstep = time.overstep_fraction();
query.iter_mut().for_each(|(mut transform, interpolation)| {
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
transform.translation = start.lerp(end, overstep);
}
});
}
fn ease_rotation_slerp(
mut query: Query<
(&mut Transform, &RotationEasingState),
(Without<NonlinearRotationEasing>, Without<NoRotationEasing>),
>,
time: Res<Time<Fixed>>,
) {
let overstep = time.overstep_fraction();
query
.par_iter_mut()
.for_each(|(mut transform, interpolation)| {
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
transform.rotation = start.slerp(end, overstep);
}
});
}
fn ease_scale_lerp(
mut query: Query<(&mut Transform, &ScaleEasingState), Without<NoScaleEasing>>,
time: Res<Time<Fixed>>,
) {
let overstep = time.overstep_fraction();
query.iter_mut().for_each(|(mut transform, interpolation)| {
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
transform.scale = start.lerp(end, overstep);
}
});
}