Skip to content

Commit b916459

Browse files
committed
Implement core functionality and API
1 parent e36533a commit b916459

File tree

2 files changed

+336
-9
lines changed

2 files changed

+336
-9
lines changed

src/interpolation.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use crate::{RotationEasingState, ScaleEasingState, TranslationEasingState};
2+
use bevy::{
3+
ecs::component::{ComponentHooks, StorageType},
4+
prelude::*,
5+
};
6+
7+
/// A bundle for enabling full [transform interpolation] for an entity.
8+
/// Changes in translation, rotation, and scale are smoothed between [`FixedUpdate`] runs.
9+
///
10+
/// [transform interpolation]: crate
11+
#[derive(Bundle, Clone, Copy, Debug, Default, PartialEq)]
12+
pub struct TransformInterpolationBundle {
13+
pub translation_interpolation: TranslationInterpolation,
14+
pub translation_easing: TranslationEasingState,
15+
pub rotation_interpolation: RotationInterpolation,
16+
pub rotation_easing: RotationEasingState,
17+
pub scale_interpolation: ScaleInterpolation,
18+
pub scale_easing: ScaleEasingState,
19+
}
20+
21+
/// Enables automatic translation interpolation for an entity.
22+
///
23+
/// Changes in translation are smoothed between [`FixedUpdate`] runs.
24+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
25+
#[reflect(Component, Debug, Default)]
26+
pub struct TranslationInterpolation;
27+
28+
/// Enables automatic rotation interpolation for an entity.
29+
///
30+
/// Changes in rotation are smoothed between [`FixedUpdate`] runs.
31+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
32+
#[reflect(Component, Debug, Default)]
33+
pub struct RotationInterpolation;
34+
35+
/// Enables automatic scale interpolation for an entity.
36+
///
37+
/// Changes in scale are smoothed between [`FixedUpdate`] runs.
38+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
39+
#[reflect(Component, Debug, Default)]
40+
pub struct ScaleInterpolation;
41+
42+
impl Component for TranslationInterpolation {
43+
const STORAGE_TYPE: StorageType = StorageType::Table;
44+
45+
fn register_component_hooks(hooks: &mut ComponentHooks) {
46+
hooks.on_add(|mut world, entity, _| {
47+
if let Some(mut easing) = world.get_mut::<TranslationEasingState>(entity) {
48+
*easing = TranslationEasingState::default();
49+
} else {
50+
world
51+
.commands()
52+
.entity(entity)
53+
.insert(TranslationEasingState::default());
54+
}
55+
});
56+
}
57+
}
58+
59+
impl Component for RotationInterpolation {
60+
const STORAGE_TYPE: StorageType = StorageType::Table;
61+
62+
fn register_component_hooks(hooks: &mut ComponentHooks) {
63+
hooks.on_add(|mut world, entity, _| {
64+
if let Some(mut easing) = world.get_mut::<RotationEasingState>(entity) {
65+
*easing = RotationEasingState::default();
66+
} else {
67+
world
68+
.commands()
69+
.entity(entity)
70+
.insert(RotationEasingState::default());
71+
}
72+
});
73+
}
74+
}
75+
76+
impl Component for ScaleInterpolation {
77+
const STORAGE_TYPE: StorageType = StorageType::Table;
78+
79+
fn register_component_hooks(hooks: &mut ComponentHooks) {
80+
hooks.on_add(|mut world, entity, _| {
81+
if let Some(mut easing) = world.get_mut::<ScaleEasingState>(entity) {
82+
*easing = ScaleEasingState::default();
83+
} else {
84+
world
85+
.commands()
86+
.entity(entity)
87+
.insert(ScaleEasingState::default());
88+
}
89+
});
90+
}
91+
}
92+
93+
pub fn update_translation_interpolation_start(
94+
mut query: Query<(&Transform, &mut TranslationEasingState), With<TranslationInterpolation>>,
95+
) {
96+
for (transform, mut easing) in &mut query {
97+
easing.start = Some(transform.translation);
98+
}
99+
}
100+
101+
pub fn update_translation_interpolation_end(
102+
mut query: Query<(&Transform, &mut TranslationEasingState), With<TranslationInterpolation>>,
103+
) {
104+
for (transform, mut easing) in &mut query {
105+
easing.end = Some(transform.translation);
106+
}
107+
}
108+
109+
pub fn update_rotation_interpolation_start(
110+
mut query: Query<(&Transform, &mut RotationEasingState), With<RotationInterpolation>>,
111+
) {
112+
for (transform, mut easing) in &mut query {
113+
easing.start = Some(transform.rotation);
114+
}
115+
}
116+
117+
pub fn update_rotation_interpolation_end(
118+
mut query: Query<(&Transform, &mut RotationEasingState), With<RotationInterpolation>>,
119+
) {
120+
for (transform, mut easing) in &mut query {
121+
easing.end = Some(transform.rotation);
122+
}
123+
}
124+
125+
pub fn update_scale_interpolation_start(
126+
mut query: Query<(&Transform, &mut ScaleEasingState), With<ScaleInterpolation>>,
127+
) {
128+
for (transform, mut easing) in &mut query {
129+
easing.start = Some(transform.scale);
130+
}
131+
}
132+
133+
pub fn update_scale_interpolation_end(
134+
mut query: Query<(&Transform, &mut ScaleEasingState), With<ScaleInterpolation>>,
135+
) {
136+
for (transform, mut easing) in &mut query {
137+
easing.end = Some(transform.scale);
138+
}
139+
}

src/lib.rs

Lines changed: 197 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,202 @@
1-
pub fn add(left: u64, right: u64) -> u64 {
2-
left + right
1+
//! General-purpose [`Transform`] interpolation for the Bevy game engine.
2+
3+
mod interpolation;
4+
5+
use bevy::prelude::*;
6+
pub use interpolation::*;
7+
8+
/// Performs transform interpolation.
9+
#[derive(Debug, Default)]
10+
pub struct TransformInterpolationPlugin {
11+
pub global_translation_interpolation: bool,
12+
pub global_rotation_interpolation: bool,
13+
pub global_scale_interpolation: bool,
14+
}
15+
16+
impl Plugin for TransformInterpolationPlugin {
17+
fn build(&self, app: &mut App) {
18+
app.register_type::<(
19+
TranslationEasingState,
20+
RotationEasingState,
21+
ScaleEasingState,
22+
)>();
23+
app.register_type::<(
24+
TranslationInterpolation,
25+
RotationInterpolation,
26+
ScaleInterpolation,
27+
)>();
28+
29+
app.configure_sets(
30+
PostUpdate,
31+
TransformInterpolationSet.before(TransformSystem::TransformPropagate),
32+
);
33+
34+
app.add_systems(
35+
FixedFirst,
36+
(
37+
(
38+
reset_translation_interpolation,
39+
reset_rotation_interpolation,
40+
reset_scale_interpolation,
41+
),
42+
(
43+
update_translation_interpolation_start,
44+
update_rotation_interpolation_start,
45+
update_scale_interpolation_start,
46+
),
47+
)
48+
.chain(),
49+
);
50+
app.add_systems(
51+
FixedLast,
52+
(
53+
update_translation_interpolation_end,
54+
update_rotation_interpolation_end,
55+
update_scale_interpolation_end,
56+
),
57+
);
58+
59+
app.add_systems(
60+
PostUpdate,
61+
(ease_translation, ease_rotation, ease_scale).in_set(TransformInterpolationSet),
62+
);
63+
64+
let interpolate_translation = self.global_translation_interpolation;
65+
let interpolate_rotation = self.global_rotation_interpolation;
66+
let interpolate_scale = self.global_scale_interpolation;
67+
68+
app.observe(
69+
move |trigger: Trigger<OnAdd, Transform>, mut commands: Commands| {
70+
if interpolate_translation {
71+
commands
72+
.entity(trigger.entity())
73+
.insert(TranslationInterpolation);
74+
}
75+
if interpolate_rotation {
76+
commands
77+
.entity(trigger.entity())
78+
.insert(RotationInterpolation);
79+
}
80+
if interpolate_scale {
81+
commands.entity(trigger.entity()).insert(ScaleInterpolation);
82+
}
83+
},
84+
);
85+
}
86+
}
87+
88+
/// A system set for [transform interpolation]. Runs in [`PostUpdate`], before [`TransformSystem::TransformPropagate`].
89+
///
90+
/// [transform interpolation]: TransformInterpolation
91+
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
92+
pub struct TransformInterpolationSet;
93+
94+
/// Stores the start and end states used for interpolating the translation of an entity.
95+
/// The change in translation is smoothed from `start` to `end` in between [`FixedUpdate`] runs.
96+
///
97+
/// On its own, this component is not updated automatically. To perform automatic interpolation,
98+
/// add the [`TranslationInterpolation`] component.
99+
100+
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
101+
#[reflect(Component, Debug, Default)]
102+
pub struct TranslationEasingState {
103+
pub start: Option<Vec3>,
104+
pub end: Option<Vec3>,
105+
}
106+
107+
/// Stores the start and end states used for interpolating the rotation of an entity.
108+
/// The change in rotation is smoothed from `start` to `end` in between [`FixedUpdate`] runs.
109+
///
110+
/// On its own, this component is not updated automatically. To perform automatic interpolation,
111+
/// add the [`RotationInterpolation`] component.
112+
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
113+
#[reflect(Component, Debug, Default)]
114+
pub struct RotationEasingState {
115+
pub start: Option<Quat>,
116+
pub end: Option<Quat>,
117+
}
118+
119+
/// Stores the start and end states used for interpolating the scale of an entity.
120+
/// The change in scale is smoothed from `start` to `end` in between [`FixedUpdate`] runs.
121+
///
122+
/// On its own, this component is not updated automatically. To perform automatic interpolation,
123+
/// add the [`ScaleInterpolation`] component.
124+
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
125+
#[reflect(Component, Debug, Default)]
126+
pub struct ScaleEasingState {
127+
pub start: Option<Vec3>,
128+
pub end: Option<Vec3>,
129+
}
130+
131+
fn reset_translation_interpolation(
132+
mut query: Query<(&mut Transform, &mut TranslationEasingState)>,
133+
) {
134+
for (mut transform, mut easing) in &mut query {
135+
// Make sure the previous easing is fully applied.
136+
if let Some(end) = easing.end {
137+
transform.translation = end;
138+
}
139+
140+
easing.start = None;
141+
easing.end = None;
142+
}
143+
}
144+
145+
fn reset_rotation_interpolation(mut query: Query<(&mut Transform, &mut RotationEasingState)>) {
146+
for (mut transform, mut easing) in &mut query {
147+
// Make sure the previous easing is fully applied.
148+
if let Some(end) = easing.end {
149+
transform.rotation = end;
150+
}
151+
152+
easing.start = None;
153+
easing.end = None;
154+
}
3155
}
4156

5-
#[cfg(test)]
6-
mod tests {
7-
use super::*;
157+
fn reset_scale_interpolation(mut query: Query<(&mut Transform, &mut ScaleEasingState)>) {
158+
for (mut transform, mut easing) in &mut query {
159+
// Make sure the previous easing is fully applied.
160+
if let Some(end) = easing.end {
161+
transform.scale = end;
162+
}
8163

9-
#[test]
10-
fn it_works() {
11-
let result = add(2, 2);
12-
assert_eq!(result, 4);
164+
easing.start = None;
165+
easing.end = None;
13166
}
14167
}
168+
169+
fn ease_translation(
170+
mut query: Query<(&mut Transform, &TranslationEasingState)>,
171+
time: Res<Time<Fixed>>,
172+
) {
173+
let overstep = time.overstep_fraction();
174+
175+
query.iter_mut().for_each(|(mut transform, interpolation)| {
176+
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
177+
transform.translation = start.lerp(end, overstep);
178+
}
179+
});
180+
}
181+
182+
fn ease_rotation(mut query: Query<(&mut Transform, &RotationEasingState)>, time: Res<Time<Fixed>>) {
183+
let overstep = time.overstep_fraction();
184+
185+
query
186+
.par_iter_mut()
187+
.for_each(|(mut transform, interpolation)| {
188+
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
189+
transform.rotation = start.slerp(end, overstep);
190+
}
191+
});
192+
}
193+
194+
fn ease_scale(mut query: Query<(&mut Transform, &ScaleEasingState)>, time: Res<Time<Fixed>>) {
195+
let overstep = time.overstep_fraction();
196+
197+
query.iter_mut().for_each(|(mut transform, interpolation)| {
198+
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) {
199+
transform.scale = start.lerp(end, overstep);
200+
}
201+
});
202+
}

0 commit comments

Comments
 (0)