bevy_transform_interpolation/extrapolation.rs
1//! [`Transform`] extrapolation, making movement in [`FixedUpdate`] appear smooth
2//! by easing between the current and predicted [`Transform`] in between fixed timesteps.
3//!
4//! See the [`TransformExtrapolationPlugin`] for more information.
5
6use core::marker::PhantomData;
7
8use crate::{
9 NoRotationEasing, NoTranslationEasing, RotationEasingState, TransformEasingPlugin,
10 TransformEasingSet, TranslationEasingState, VelocitySource, VelocitySourceItem,
11};
12use bevy::prelude::*;
13
14/// A plugin for [`Transform`] extrapolation, making movement in [`FixedUpdate`] appear smooth.
15///
16/// Transform extrapolation predicts future positions based on velocity, and applies easing
17/// between the current and predicted [`Transform`] in between fixed timesteps.
18/// This results in movement that looks smooth and feels responsive, but can stutter
19/// when the prediction is incorrect, such as when velocity changes abruptly.
20///
21/// This plugin requires the [`TransformEasingPlugin`] to function. It is automatically added
22/// if not already present in the app.
23///
24/// Note that unlike [`TransformInterpolationPlugin`], this plugin does *not* support scale easing.
25/// However, the [`ScaleInterpolation`] component can still be used even when translation and rotation are extrapolated.
26///
27/// [`TransformInterpolationPlugin`]: crate::interpolation::TransformInterpolationPlugin
28/// [`ScaleInterpolation`]: crate::interpolation::ScaleInterpolation
29///
30/// # Usage
31///
32/// Transform extrapolation requires velocity to predict future positions.
33/// Instead of providing its own velocity components, the [`TransformExtrapolationPlugin`]
34/// lets you specify your own velocity components that you manage yourself.
35///
36/// First, make sure you have components for velocity, and implement the [`VelocitySource`] trait on a [`QueryData`] type:
37///
38/// ```
39/// use bevy::{ecs::query::QueryData, prelude::*};
40/// use bevy_transform_interpolation::VelocitySource;
41///
42/// #[derive(Component, Default)]
43/// struct LinearVelocity(Vec3);
44///
45/// #[derive(Component, Default)]
46/// struct AngularVelocity(Vec3);
47///
48/// #[derive(QueryData)]
49/// struct LinVelSource;
50///
51/// impl VelocitySource for LinVelSource {
52/// // Components storing the previous and current velocities.
53/// // Note: For extrapolation, the `Previous` component is not used, so we can make it the same as `Current`.
54/// type Previous = LinearVelocity;
55/// type Current = LinearVelocity;
56///
57/// fn previous(start: &Self::Previous) -> Vec3 {
58/// start.0
59/// }
60///
61/// fn current(end: &Self::Current) -> Vec3 {
62/// end.0
63/// }
64/// }
65///
66/// #[derive(QueryData)]
67/// struct AngVelSource;
68///
69/// impl VelocitySource for AngVelSource {
70/// type Previous = AngularVelocity;
71/// type Current = AngularVelocity;
72///
73/// fn previous(start: &Self::Previous) -> Vec3 {
74/// start.0
75/// }
76///
77/// fn current(end: &Self::Current) -> Vec3 {
78/// end.0
79/// }
80/// }
81/// ```
82///
83/// Then, add the [`TransformExtrapolationPlugin`] to the app with the velocity sources:
84///
85/// ```no_run
86/// use bevy::{ecs::query::QueryData, prelude::*};
87/// use bevy_transform_interpolation::{prelude::*, VelocitySource};
88/// #
89/// # #[derive(Component, Default)]
90/// # struct LinearVelocity(Vec3);
91/// #
92/// # #[derive(Component, Default)]
93/// # struct AngularVelocity(Vec3);
94/// #
95/// # #[derive(QueryData)]
96/// # struct LinVelSource;
97/// #
98/// # impl VelocitySource for LinVelSource {
99/// # type Previous = LinearVelocity;
100/// # type Current = LinearVelocity;
101/// #
102/// # fn previous(start: &Self::Previous) -> Vec3 {
103/// # start.0
104/// # }
105/// #
106/// # fn current(end: &Self::Current) -> Vec3 {
107/// # end.0
108/// # }
109/// # }
110/// #
111/// # #[derive(QueryData)]
112/// # struct AngVelSource;
113/// #
114/// # impl VelocitySource for AngVelSource {
115/// # type Previous = AngularVelocity;
116/// # type Current = AngularVelocity;
117/// #
118/// # fn previous(start: &Self::Previous) -> Vec3 {
119/// # start.0
120/// # }
121/// #
122/// # fn current(end: &Self::Current) -> Vec3 {
123/// # end.0
124/// # }
125/// # }
126///
127/// fn main() {
128/// let mut app = App::new();
129///
130/// app.add_plugins((
131/// TransformInterpolationPlugin::default(),
132/// TransformExtrapolationPlugin::<LinVelSource, AngVelSource>::default(),
133/// # bevy::time::TimePlugin::default(),
134/// ));
135///
136/// // Optional: Insert velocity components automatically for entities with extrapolation.
137/// app.register_required_components::<TranslationExtrapolation, LinearVelocity>();
138/// app.register_required_components::<RotationExtrapolation, AngularVelocity>();
139///
140/// // ...
141///
142/// app.run();
143/// }
144/// ```
145///
146/// Transform extrapolation can now be enabled for a given entity by adding the [`TransformExtrapolation`] component:
147///
148/// ```
149/// # use bevy::prelude::*;
150/// # use bevy_transform_interpolation::prelude::*;
151/// #
152/// fn setup(mut commands: Commands) {
153/// // Extrapolate translation and rotation.
154/// commands.spawn((
155/// Transform::default(),
156/// TransformExtrapolation,
157/// ));
158/// }
159/// ```
160///
161/// Now, any changes made to the translation or rotation of the entity in [`FixedPreUpdate`], [`FixedUpdate`],
162/// or [`FixedPostUpdate`] will automatically be smoothed in between fixed timesteps.
163///
164/// Transform properties can also be extrapolated individually by adding the [`TranslationExtrapolation`]
165/// and [`RotationExtrapolation`] components.
166///
167/// ```
168/// # use bevy::prelude::*;
169/// # use bevy_transform_interpolation::prelude::*;
170/// #
171/// fn setup(mut commands: Commands) {
172/// // Only extrapolate translation.
173/// commands.spawn((Transform::default(), TranslationExtrapolation));
174///
175/// // Only extrapolate rotation.
176/// commands.spawn((Transform::default(), RotationExtrapolation));
177/// }
178/// ```
179///
180/// If you want *all* entities with a [`Transform`] to be extrapolated by default, you can use
181/// [`TransformExtrapolationPlugin::extrapolate_all()`], or set the [`extrapolate_translation_all`]
182/// and [`extrapolate_rotation_all`] fields.
183///
184/// ```ignore
185/// # use bevy::prelude::*;
186/// # use bevy_transform_interpolation::prelude::*;
187/// #
188/// fn main() {
189/// App::new()
190/// .add_plugins(TransformExtrapolationPlugin::<LinVelSource, AngVelSource> {
191/// // Extrapolate translation by default, but not rotation.
192/// extrapolate_translation_all: true,
193/// extrapolate_rotation_all: false,
194/// })
195/// // ...
196/// .run();
197/// }
198/// ```
199///
200/// When extrapolation is enabled for all entities by default, you can still opt out of it for individual entities
201/// by adding the [`NoTransformEasing`] component, or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
202///
203/// Note that changing [`Transform`] manually in any schedule that *doesn't* use a fixed timestep is also supported,
204/// but it is equivalent to teleporting, and disables extrapolation for the entity for the remainder of that fixed timestep.
205///
206/// [`QueryData`]: bevy::ecs::query::QueryData
207/// [`TransformExtrapolationPlugin::extrapolate_all()`]: TransformExtrapolationPlugin::extrapolate_all
208/// [`extrapolate_translation_all`]: TransformExtrapolationPlugin::extrapolate_translation_all
209/// [`extrapolate_rotation_all`]: TransformExtrapolationPlugin::extrapolate_rotation_all
210/// [`NoTransformEasing`]: crate::NoTransformEasing
211/// [`NoTranslationEasing`]: crate::NoTranslationEasing
212/// [`NoRotationEasing`]: crate::NoRotationEasing
213///
214/// # Alternatives
215///
216/// For many applications, the stutter caused by mispredictions in extrapolation may be undesirable.
217/// In these cases, the [`TransformInterpolationPlugin`] can be a better alternative.
218///
219/// Transform interpolation eases between the previous and current [`Transform`],
220/// resulting in movement that is always smooth and accurate. The downside is that the rendered
221/// positions can lag slightly behind the true positions, making movement feel delayed.
222///
223/// # Easing Backends
224///
225/// By default, transform extrapolation uses linear interpolation (`lerp`) for easing translation,
226/// and spherical linear interpolation (`slerp`) for easing rotation.
227///
228/// If the previous and current velocities are also available, it is possible to use *Hermite interpolation*
229/// with the [`TransformHermiteEasingPlugin`] to get smoother and more accurate easing. To enable Hermite interpolation
230/// for extrapolation, add the [`TransformHermiteEasing`] component to the entity in addition to the extrapolation components.
231///
232/// [`TransformHermiteEasingPlugin`]: crate::hermite::TransformHermiteEasingPlugin
233/// [`TransformHermiteEasing`]: crate::hermite::TransformHermiteEasing
234#[derive(Debug)]
235pub struct TransformExtrapolationPlugin<LinVel: VelocitySource, AngVel: VelocitySource> {
236 /// If `true`, translation will be extrapolated for all entities with the [`Transform`] component by default.
237 ///
238 /// This can be overridden for individual entities by adding the [`NoTranslationEasing`] or [`NoTransformEasing`] component.
239 ///
240 /// [`NoTransformEasing`]: crate::NoTransformEasing
241 pub extrapolate_translation_all: bool,
242 /// If `true`, rotation will be extrapolated for all entities with the [`Transform`] component by default.
243 ///
244 /// This can be overridden for individual entities by adding the [`NoRotationEasing`] or [`NoTransformEasing`] component.
245 ///
246 /// [`NoTransformEasing`]: crate::NoTransformEasing
247 pub extrapolate_rotation_all: bool,
248 /// Phantom data use the type parameters.
249 #[doc(hidden)]
250 pub _phantom: PhantomData<(LinVel, AngVel)>,
251}
252
253impl<LinVel: VelocitySource, AngVel: VelocitySource> Default
254 for TransformExtrapolationPlugin<LinVel, AngVel>
255{
256 fn default() -> Self {
257 Self {
258 extrapolate_translation_all: false,
259 extrapolate_rotation_all: false,
260 _phantom: PhantomData,
261 }
262 }
263}
264
265impl<LinVel: VelocitySource, AngVel: VelocitySource> TransformExtrapolationPlugin<LinVel, AngVel> {
266 /// Enables extrapolation for translation and rotation for all entities with the [`Transform`] component.
267 ///
268 /// This can be overridden for individual entities by adding the [`NoTransformEasing`] component,
269 /// or the individual [`NoTranslationEasing`] and [`NoRotationEasing`] components.
270 ///
271 /// [`NoTransformEasing`]: crate::NoTransformEasing
272 /// [`NoRotationEasing`]: crate::NoRotationEasing
273 pub fn extrapolate_all() -> Self {
274 Self {
275 extrapolate_translation_all: true,
276 extrapolate_rotation_all: true,
277 _phantom: PhantomData,
278 }
279 }
280}
281
282impl<LinVel: VelocitySource, AngVel: VelocitySource> Plugin
283 for TransformExtrapolationPlugin<LinVel, AngVel>
284{
285 fn build(&self, app: &mut App) {
286 //Register components.
287 app.register_type::<(
288 TransformExtrapolation,
289 TranslationExtrapolation,
290 RotationExtrapolation,
291 )>();
292
293 // Reset the transform to the start of the extrapolation at the beginning of the fixed timestep
294 // to match the true position from the end of the previous fixed tick.
295 app.add_systems(
296 FixedFirst,
297 (
298 reset_translation_extrapolation,
299 reset_rotation_extrapolation,
300 )
301 .before(TransformEasingSet::Reset),
302 );
303
304 // Update the start and end state of the extrapolation at the end of the fixed timestep.
305 app.add_systems(
306 FixedLast,
307 (
308 update_translation_extrapolation_states::<LinVel>,
309 update_rotation_extrapolation_states::<AngVel>,
310 )
311 .in_set(TransformEasingSet::UpdateEnd),
312 );
313
314 // Insert extrapolation components automatically for all entities with a `Transform`
315 // if the corresponding global extrapolation is enabled.
316 if self.extrapolate_translation_all {
317 let _ = app.try_register_required_components::<Transform, TranslationExtrapolation>();
318 }
319 if self.extrapolate_rotation_all {
320 let _ = app.try_register_required_components::<Transform, RotationExtrapolation>();
321 }
322 }
323
324 fn finish(&self, app: &mut App) {
325 // Add the `TransformEasingPlugin` if it hasn't been added yet.
326 // It performs the actual easing based on the start and end states set by the extrapolation.
327 if !app.is_plugin_added::<TransformEasingPlugin>() {
328 app.add_plugins(TransformEasingPlugin);
329 }
330 }
331}
332
333/// Enables [`Transform`] extrapolation for an entity, making changes to translation
334/// and rotation in [`FixedUpdate`] appear smooth.
335///
336/// Extrapolation only works for entities with velocity components.
337/// [`TransformExtrapolationPlugin`] must be added to the app with the appropriate velocity sources.
338///
339/// Unlike [`TransformInterpolation`], this does *not* support scale easing.
340/// However, the [`ScaleInterpolation`] component can still be used even when translation and rotation are extrapolated.
341///
342/// See the [`TransformExtrapolationPlugin`] for more information.
343///
344/// [`TransformInterpolation`]: crate::interpolation::TransformInterpolation
345/// [`ScaleInterpolation`]: crate::interpolation::ScaleInterpolation
346#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
347#[reflect(Component, Debug, Default)]
348#[require(TranslationExtrapolation, RotationExtrapolation)]
349pub struct TransformExtrapolation;
350
351/// Enables translation extrapolation for an entity, making changes to translation
352/// in [`FixedUpdate`] appear smooth.
353///
354/// Extrapolation only works for entities with velocity components.
355/// [`TransformExtrapolationPlugin`] must be added to the app with the appropriate velocity sources.
356///
357/// See the [`TransformExtrapolationPlugin`] for more information.
358#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
359#[reflect(Component, Debug, Default)]
360#[require(TranslationEasingState)]
361pub struct TranslationExtrapolation;
362
363/// Enables rotation extrapolation for an entity, making changes to rotation
364/// in [`FixedUpdate`] appear smooth.
365///
366/// Extrapolation only works for entities with velocity components.
367/// [`TransformExtrapolationPlugin`] must be added to the app with the appropriate velocity sources.
368///
369/// See the [`TransformExtrapolationPlugin`] for more information.
370#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
371#[reflect(Component, Debug, Default)]
372#[require(RotationEasingState)]
373pub struct RotationExtrapolation;
374
375/// Resets the translation to the start of the extrapolation at the beginning of the fixed timestep
376/// to match the true position from the end of the previous fixed tick.
377fn reset_translation_extrapolation(
378 mut query: Query<
379 (&mut Transform, &TranslationEasingState),
380 (With<TranslationExtrapolation>, Without<NoTranslationEasing>),
381 >,
382) {
383 for (mut transform, translation_easing) in &mut query {
384 if let Some(start) = translation_easing.start {
385 transform.translation = start;
386 }
387 }
388}
389
390/// Resets the rotation to the start of the extrapolation at the beginning of the fixed timestep
391/// to match the true position from the end of the previous fixed tick.
392fn reset_rotation_extrapolation(
393 mut query: Query<
394 (&mut Transform, &RotationEasingState),
395 (With<RotationExtrapolation>, Without<NoRotationEasing>),
396 >,
397) {
398 for (mut transform, rotation_easing) in &mut query {
399 if let Some(start) = rotation_easing.start {
400 transform.rotation = start;
401 }
402 }
403}
404
405/// Updates the start and end states of the extrapolation for the next fixed timestep.
406fn update_translation_extrapolation_states<V: VelocitySource>(
407 mut query: Query<
408 (&Transform, &mut TranslationEasingState, &V::Current),
409 (With<TranslationExtrapolation>, Without<NoTranslationEasing>),
410 >,
411 time: Res<Time>,
412) {
413 let delta_secs = time.delta_secs();
414
415 for (transform, mut translation_easing, end_vel) in &mut query {
416 translation_easing.start = Some(transform.translation);
417
418 // Extrapolate the next state based on the current state and velocities.
419 let lin_vel = <V::Item<'static> as VelocitySourceItem<V>>::current(end_vel);
420 translation_easing.end = Some(transform.translation + lin_vel * delta_secs);
421 }
422}
423
424/// Updates the start and end states of the extrapolation for the next fixed timestep.
425fn update_rotation_extrapolation_states<V: VelocitySource>(
426 mut query: Query<
427 (&Transform, &mut RotationEasingState, &V::Current),
428 (With<RotationExtrapolation>, Without<NoRotationEasing>),
429 >,
430 time: Res<Time>,
431) {
432 let delta_secs = time.delta_secs();
433
434 for (transform, mut rotation_easing, end_vel) in &mut query {
435 rotation_easing.start = Some(transform.rotation);
436
437 // Extrapolate the next state based on the current state and velocities.
438 let ang_vel = <V::Item<'static> as VelocitySourceItem<V>>::current(end_vel);
439 let scaled_axis = ang_vel * delta_secs;
440 rotation_easing.end = Some(transform.rotation * Quat::from_scaled_axis(scaled_axis));
441 }
442}