bevy_tweening/
lens.rs

1//! Collection of predefined lenses for common Bevy components and assets.
2//!
3//! # Predefined lenses
4//!
5//! This module contains predefined lenses for common use cases. Those lenses
6//! are entirely optional. They can be used if they fit your use case, to save
7//! some time, but are not treated any differently from a custom user-provided
8//! lens.
9//!
10//! # Rotations
11//!
12//! Several rotation lenses are provided, with different properties.
13//!
14//! ## Shortest-path rotation
15//!
16//! The [`TransformRotationLens`] animates the [`rotation`] field of a
17//! [`Transform`] component using [`Quat::slerp()`]. It inherits the properties
18//! of that method, and in particular the fact it always finds the "shortest
19//! path" from start to end. This is well suited for animating a rotation
20//! between two given directions, but will provide unexpected results if you try
21//! to make an entity rotate around a given axis for more than half a turn, as
22//! [`Quat::slerp()`] will then try to move "the other way around".
23//!
24//! ## Angle-focused rotations
25//!
26//! Conversely, for cases where the rotation direction is important, like when
27//! trying to do a full 360-degree turn, a series of angle-based interpolation
28//! lenses is provided:
29//! - [`TransformRotateXLens`]
30//! - [`TransformRotateYLens`]
31//! - [`TransformRotateZLens`]
32//! - [`TransformRotateAxisLens`]
33//!
34//! [`rotation`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html#structfield.rotation
35//! [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
36//! [`Quat::slerp()`]: https://docs.rs/bevy/0.17/bevy/math/struct.Quat.html#method.slerp
37
38use bevy::prelude::*;
39
40/// A lens over a subset of a component.
41///
42/// The lens takes a `target` component or asset from a query, as a mutable
43/// reference, and animates (tweens) a subset of the fields of the
44/// component/asset based on the linear ratio `ratio` in \[0:1\], already
45/// sampled from the easing curve.
46///
47/// # Example
48///
49/// Implement `Lens` for a custom type:
50///
51/// ```rust
52/// # use bevy::prelude::*;
53/// # use bevy_tweening::*;
54/// struct MyLens {
55///     start: f32,
56///     end: f32,
57/// }
58///
59/// #[derive(Component)]
60/// struct MyStruct(f32);
61///
62/// impl Lens<MyStruct> for MyLens {
63///     fn lerp(&mut self, mut target: Mut<MyStruct>, ratio: f32) {
64///         target.0 = self.start + (self.end - self.start) * ratio;
65///     }
66/// }
67/// ```
68pub trait Lens<T> {
69    /// Perform a linear interpolation (lerp) over the subset of fields of a
70    /// component or asset the lens focuses on, based on the linear ratio
71    /// `ratio`. The `target` component or asset is mutated in place. The
72    /// implementation decides which fields are interpolated, and performs
73    /// the animation in-place, overwriting the target.
74    fn lerp(&mut self, target: Mut<'_, T>, ratio: f32);
75}
76
77/// A lens to manipulate the [`color`] field of a section of a [`Text`]
78/// component.
79///
80/// [`color`]: https://docs.rs/bevy/0.17/bevy/text/struct.TextColor.html
81#[cfg(feature = "bevy_text")]
82#[derive(Debug, Copy, Clone, PartialEq)]
83pub struct TextColorLens {
84    /// Start color.
85    pub start: Color,
86    /// End color.
87    pub end: Color,
88}
89
90#[cfg(feature = "bevy_text")]
91impl Lens<TextColor> for TextColorLens {
92    fn lerp(&mut self, mut target: Mut<TextColor>, ratio: f32) {
93        target.0 = self.start.mix(&self.end, ratio);
94    }
95}
96
97/// A lens to manipulate the [`translation`] field of a [`Transform`] component.
98///
99/// [`translation`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html#structfield.translation
100/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
101#[derive(Debug, Copy, Clone, PartialEq)]
102pub struct TransformPositionLens {
103    /// Start value of the translation.
104    pub start: Vec3,
105    /// End value of the translation.
106    pub end: Vec3,
107}
108
109impl Lens<Transform> for TransformPositionLens {
110    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
111        target.translation = self.start.lerp(self.end, ratio);
112    }
113}
114
115/// A lens to manipulate the [`rotation`] field of a [`Transform`] component.
116///
117/// This lens interpolates the [`rotation`] field of a [`Transform`] component
118/// from a `start` value to an `end` value using the spherical linear
119/// interpolation provided by [`Quat::slerp()`]. This means the rotation always
120/// uses the shortest path from `start` to `end`. In particular, this means it
121/// cannot make entities do a full 360 degrees turn. Instead use
122/// [`TransformRotateXLens`] and similar to interpolate the rotation angle
123/// around a given axis.
124///
125/// See the [top-level `lens` module documentation] for a comparison of rotation
126/// lenses.
127///
128/// [`rotation`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html#structfield.rotation
129/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
130/// [`Quat::slerp()`]: https://docs.rs/bevy/0.17/bevy/math/struct.Quat.html#method.slerp
131/// [top-level `lens` module documentation]: crate::lens
132#[derive(Debug, Copy, Clone, PartialEq)]
133pub struct TransformRotationLens {
134    /// Start value of the rotation.
135    pub start: Quat,
136    /// End value of the rotation.
137    pub end: Quat,
138}
139
140impl Lens<Transform> for TransformRotationLens {
141    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
142        target.rotation = self.start.slerp(self.end, ratio);
143    }
144}
145
146/// A lens to rotate a [`Transform`] component around its local X axis.
147///
148/// This lens interpolates the rotation angle of a [`Transform`] component from
149/// a `start` value to an `end` value, for a rotation around the X axis. Unlike
150/// [`TransformRotationLens`], it can produce an animation that rotates the
151/// entity any number of turns around its local X axis.
152///
153/// See the [top-level `lens` module documentation] for a comparison of rotation
154/// lenses.
155///
156/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
157/// [top-level `lens` module documentation]: crate::lens
158#[derive(Debug, Copy, Clone, PartialEq)]
159pub struct TransformRotateXLens {
160    /// Start value of the rotation angle, in radians.
161    pub start: f32,
162    /// End value of the rotation angle, in radians.
163    pub end: f32,
164}
165
166impl Lens<Transform> for TransformRotateXLens {
167    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
168        let angle = (self.end - self.start).mul_add(ratio, self.start);
169        target.rotation = Quat::from_rotation_x(angle);
170    }
171}
172
173/// A lens to rotate a [`Transform`] component around its local Y axis.
174///
175/// This lens interpolates the rotation angle of a [`Transform`] component from
176/// a `start` value to an `end` value, for a rotation around the Y axis. Unlike
177/// [`TransformRotationLens`], it can produce an animation that rotates the
178/// entity any number of turns around its local Y axis.
179///
180/// See the [top-level `lens` module documentation] for a comparison of rotation
181/// lenses.
182///
183/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
184/// [top-level `lens` module documentation]: crate::lens
185#[derive(Debug, Copy, Clone, PartialEq)]
186pub struct TransformRotateYLens {
187    /// Start value of the rotation angle, in radians.
188    pub start: f32,
189    /// End value of the rotation angle, in radians.
190    pub end: f32,
191}
192
193impl Lens<Transform> for TransformRotateYLens {
194    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
195        let angle = (self.end - self.start).mul_add(ratio, self.start);
196        target.rotation = Quat::from_rotation_y(angle);
197    }
198}
199
200/// A lens to rotate a [`Transform`] component around its local Z axis.
201///
202/// This lens interpolates the rotation angle of a [`Transform`] component from
203/// a `start` value to an `end` value, for a rotation around the Z axis. Unlike
204/// [`TransformRotationLens`], it can produce an animation that rotates the
205/// entity any number of turns around its local Z axis.
206///
207/// See the [top-level `lens` module documentation] for a comparison of rotation
208/// lenses.
209///
210/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
211/// [top-level `lens` module documentation]: crate::lens
212#[derive(Debug, Copy, Clone, PartialEq)]
213pub struct TransformRotateZLens {
214    /// Start value of the rotation angle, in radians.
215    pub start: f32,
216    /// End value of the rotation angle, in radians.
217    pub end: f32,
218}
219
220impl Lens<Transform> for TransformRotateZLens {
221    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
222        let angle = (self.end - self.start).mul_add(ratio, self.start);
223        target.rotation = Quat::from_rotation_z(angle);
224    }
225}
226
227/// A lens to rotate a [`Transform`] component around its local X axis
228/// additively.
229///
230/// This lens interpolates the rotation angle of a local rotation from
231/// a `start` value to an `end` value, for a rotation around the local X axis,
232/// and compose this with the `base_rotation`, applying the result to a
233/// [`Transform`] component. Unlike [`TransformRotationLens`], it can produce an
234/// animation that rotates the entity any number of turns around its local X
235/// axis.
236///
237/// See the [top-level `lens` module documentation] for a comparison of rotation
238/// lenses.
239///
240/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
241/// [top-level `lens` module documentation]: crate::lens
242#[derive(Debug, Copy, Clone, PartialEq)]
243pub struct TransformRotateAdditiveXLens {
244    /// The base rotation of the object, which is composed with the animated
245    /// rotation.
246    pub base_rotation: Quat,
247    /// Start value of the rotation angle, in radians.
248    pub start: f32,
249    /// End value of the rotation angle, in radians.
250    pub end: f32,
251}
252
253impl Lens<Transform> for TransformRotateAdditiveXLens {
254    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
255        let angle = (self.end - self.start).mul_add(ratio, self.start);
256        target.rotation = self.base_rotation * Quat::from_rotation_x(angle);
257    }
258}
259
260/// A lens to rotate a [`Transform`] component around its local Y axis
261/// additively.
262///
263/// This lens interpolates the rotation angle of a local rotation from
264/// a `start` value to an `end` value, for a rotation around the local Y axis,
265/// and compose this with the `base_rotation`, applying the result to a
266/// [`Transform`] component. Unlike [`TransformRotationLens`], it can produce an
267/// animation that rotates the entity any number of turns around its local Y
268/// axis.
269///
270/// See the [top-level `lens` module documentation] for a comparison of rotation
271/// lenses.
272///
273/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
274/// [top-level `lens` module documentation]: crate::lens
275#[derive(Debug, Copy, Clone, PartialEq)]
276pub struct TransformRotateAdditiveYLens {
277    /// The base rotation of the object, which is composed with the animated
278    /// rotation.
279    pub base_rotation: Quat,
280    /// Start value of the rotation angle, in radians.
281    pub start: f32,
282    /// End value of the rotation angle, in radians.
283    pub end: f32,
284}
285
286impl Lens<Transform> for TransformRotateAdditiveYLens {
287    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
288        let angle = (self.end - self.start).mul_add(ratio, self.start);
289        target.rotation = self.base_rotation * Quat::from_rotation_y(angle);
290    }
291}
292
293/// A lens to rotate a [`Transform`] component around its local Z axis
294/// additively.
295///
296/// This lens interpolates the rotation angle of a local rotation from
297/// a `start` value to an `end` value, for a rotation around the local Z axis,
298/// and compose this with the `base_rotation`, applying the result to a
299/// [`Transform`] component. Unlike [`TransformRotationLens`], it can produce an
300/// animation that rotates the entity any number of turns around its local Z
301/// axis.
302///
303/// See the [top-level `lens` module documentation] for a comparison of rotation
304/// lenses.
305///
306/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
307/// [top-level `lens` module documentation]: crate::lens
308#[derive(Debug, Copy, Clone, PartialEq)]
309pub struct TransformRotateAdditiveZLens {
310    /// The base rotation of the object, which is composed with the animated
311    /// rotation.
312    pub base_rotation: Quat,
313    /// Start value of the rotation angle, in radians.
314    pub start: f32,
315    /// End value of the rotation angle, in radians.
316    pub end: f32,
317}
318
319impl Lens<Transform> for TransformRotateAdditiveZLens {
320    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
321        let angle = (self.end - self.start).mul_add(ratio, self.start);
322        target.rotation = self.base_rotation * Quat::from_rotation_z(angle);
323    }
324}
325
326/// A lens to rotate a [`Transform`] component around a given fixed axis.
327///
328/// This lens interpolates the rotation angle of a [`Transform`] component from
329/// a `start` value to an `end` value, for a rotation around a given axis.
330/// Unlike [`TransformRotationLens`], it can produce an animation that rotates
331/// the entity any number of turns around that axis.
332///
333/// See the [top-level `lens` module documentation] for a comparison of rotation
334/// lenses.
335///
336/// # Panics
337///
338/// This method panics if the `axis` vector is not normalized.
339///
340/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
341/// [top-level `lens` module documentation]: crate::lens
342#[derive(Debug, Copy, Clone, PartialEq)]
343pub struct TransformRotateAxisLens {
344    /// The normalized rotation axis.
345    pub axis: Vec3,
346    /// Start value of the rotation angle, in radians.
347    pub start: f32,
348    /// End value of the rotation angle, in radians.
349    pub end: f32,
350}
351
352impl Lens<Transform> for TransformRotateAxisLens {
353    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
354        let angle = (self.end - self.start).mul_add(ratio, self.start);
355        target.rotation = Quat::from_axis_angle(self.axis, angle);
356    }
357}
358
359/// A lens to manipulate the [`scale`] field of a [`Transform`] component.
360///
361/// [`scale`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html#structfield.scale
362/// [`Transform`]: https://docs.rs/bevy/0.17/bevy/transform/components/struct.Transform.html
363#[derive(Debug, Copy, Clone, PartialEq)]
364pub struct TransformScaleLens {
365    /// Start value of the scale.
366    pub start: Vec3,
367    /// End value of the scale.
368    pub end: Vec3,
369}
370
371impl Lens<Transform> for TransformScaleLens {
372    fn lerp(&mut self, mut target: Mut<Transform>, ratio: f32) {
373        target.scale = self.start + (self.end - self.start) * ratio;
374    }
375}
376
377/// A lens to manipulate the [`position`] field of a UI [`Node`] component.
378///
379/// [`position`]: https://docs.rs/bevy/0.17/bevy/ui/struct.Node.html
380/// [`Node`]: https://docs.rs/bevy/0.17/bevy/ui/struct.Node.html
381#[cfg(feature = "bevy_ui")]
382#[derive(Debug, Copy, Clone, PartialEq)]
383pub struct UiPositionLens {
384    /// Start position.
385    pub start: UiRect,
386    /// End position.
387    pub end: UiRect,
388}
389
390#[cfg(feature = "bevy_ui")]
391fn lerp_val(start: &Val, end: &Val, ratio: f32) -> Val {
392    match (start, end) {
393        (Val::Percent(start), Val::Percent(end)) => {
394            Val::Percent((end - start).mul_add(ratio, *start))
395        }
396        (Val::Px(start), Val::Px(end)) => Val::Px((end - start).mul_add(ratio, *start)),
397        (Val::Vw(start), Val::Vw(end)) => Val::Vw((end - start).mul_add(ratio, *start)),
398        (Val::Vh(start), Val::Vh(end)) => Val::Vh((end - start).mul_add(ratio, *start)),
399        (Val::VMin(start), Val::VMin(end)) => Val::VMin((end - start).mul_add(ratio, *start)),
400        (Val::VMax(start), Val::VMax(end)) => Val::VMax((end - start).mul_add(ratio, *start)),
401        _ => *start,
402    }
403}
404
405#[cfg(feature = "bevy_ui")]
406impl Lens<Node> for UiPositionLens {
407    fn lerp(&mut self, mut target: Mut<Node>, ratio: f32) {
408        target.left = lerp_val(&self.start.left, &self.end.left, ratio);
409        target.right = lerp_val(&self.start.right, &self.end.right, ratio);
410        target.top = lerp_val(&self.start.top, &self.end.top, ratio);
411        target.bottom = lerp_val(&self.start.bottom, &self.end.bottom, ratio);
412    }
413}
414
415/// Gamer
416#[cfg(feature = "bevy_ui")]
417#[derive(Debug, Copy, Clone, PartialEq)]
418pub struct UiBackgroundColorLens {
419    /// Start position.
420    pub start: Color,
421    /// End position.
422    pub end: Color,
423}
424
425#[cfg(feature = "bevy_ui")]
426impl Lens<BackgroundColor> for UiBackgroundColorLens {
427    fn lerp(&mut self, mut target: Mut<BackgroundColor>, ratio: f32) {
428        target.0 = self.start.mix(&self.end, ratio);
429    }
430}
431
432/// A lens to manipulate the [`color`] field of a [`ColorMaterial`] asset.
433///
434/// [`color`]: https://docs.rs/bevy/0.17/bevy/sprite/struct.ColorMaterial.html#structfield.color
435/// [`ColorMaterial`]: https://docs.rs/bevy/0.17/bevy/sprite/struct.ColorMaterial.html
436#[cfg(feature = "bevy_sprite")]
437#[derive(Debug, Copy, Clone, PartialEq)]
438pub struct ColorMaterialColorLens {
439    /// Start color.
440    pub start: Color,
441    /// End color.
442    pub end: Color,
443}
444
445#[cfg(feature = "bevy_sprite")]
446impl Lens<ColorMaterial> for ColorMaterialColorLens {
447    fn lerp(&mut self, mut target: Mut<ColorMaterial>, ratio: f32) {
448        target.color = self.start.mix(&self.end, ratio);
449    }
450}
451
452/// A lens to manipulate the [`color`] field of a [`Sprite`] asset.
453///
454/// [`color`]: https://docs.rs/bevy/0.17/bevy/sprite/struct.Sprite.html#structfield.color
455/// [`Sprite`]: https://docs.rs/bevy/0.17/bevy/sprite/struct.Sprite.html
456#[cfg(feature = "bevy_sprite")]
457#[derive(Debug, Copy, Clone, PartialEq)]
458pub struct SpriteColorLens {
459    /// Start color.
460    pub start: Color,
461    /// End color.
462    pub end: Color,
463}
464
465#[cfg(feature = "bevy_sprite")]
466impl Lens<Sprite> for SpriteColorLens {
467    fn lerp(&mut self, mut target: Mut<Sprite>, ratio: f32) {
468        let value = self.start.mix(&self.end, ratio);
469        target.color = value;
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use std::f32::consts::TAU;
476
477    #[cfg(any(feature = "bevy_sprite", feature = "bevy_text"))]
478    use bevy::color::palettes::css::{BLUE, RED};
479    use bevy::ecs::{change_detection::MaybeLocation, component::Tick};
480
481    use super::*;
482
483    #[cfg(feature = "bevy_text")]
484    #[test]
485    fn text_color() {
486        let mut lens = TextColorLens {
487            start: RED.into(),
488            end: BLUE.into(),
489        };
490
491        let mut text_color = TextColor::default();
492
493        {
494            let mut added = Tick::new(0);
495            let mut last_changed = Tick::new(0);
496            let mut caller = MaybeLocation::caller();
497            let target = Mut::new(
498                &mut text_color,
499                &mut added,
500                &mut last_changed,
501                Tick::new(0),
502                Tick::new(0),
503                caller.as_mut(),
504            );
505
506            lens.lerp(target, 0.);
507        }
508        assert_eq!(text_color.0, RED.into());
509
510        {
511            let mut added = Tick::new(0);
512            let mut last_changed = Tick::new(0);
513            let mut caller = MaybeLocation::caller();
514            let target = Mut::new(
515                &mut text_color,
516                &mut added,
517                &mut last_changed,
518                Tick::new(0),
519                Tick::new(0),
520                caller.as_mut(),
521            );
522
523            lens.lerp(target, 1.);
524        }
525        assert_eq!(text_color.0, BLUE.into());
526
527        {
528            let mut added = Tick::new(0);
529            let mut last_changed = Tick::new(0);
530            let mut caller = MaybeLocation::caller();
531            let target = Mut::new(
532                &mut text_color,
533                &mut added,
534                &mut last_changed,
535                Tick::new(0),
536                Tick::new(0),
537                caller.as_mut(),
538            );
539
540            lens.lerp(target, 0.3);
541        }
542        assert_eq!(text_color.0, Color::srgba(0.7, 0., 0.3, 1.0));
543    }
544
545    #[test]
546    fn transform_position() {
547        let mut lens = TransformPositionLens {
548            start: Vec3::ZERO,
549            end: Vec3::new(1., 2., -4.),
550        };
551        let mut transform = Transform::default();
552
553        {
554            let mut added = Tick::new(0);
555            let mut last_changed = Tick::new(0);
556            let mut caller = MaybeLocation::caller();
557            let target = Mut::new(
558                &mut transform,
559                &mut added,
560                &mut last_changed,
561                Tick::new(0),
562                Tick::new(0),
563                caller.as_mut(),
564            );
565
566            lens.lerp(target, 0.);
567        }
568        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
569        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
570        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
571
572        {
573            let mut added = Tick::new(0);
574            let mut last_changed = Tick::new(0);
575            let mut caller = MaybeLocation::caller();
576            let target = Mut::new(
577                &mut transform,
578                &mut added,
579                &mut last_changed,
580                Tick::new(0),
581                Tick::new(0),
582                caller.as_mut(),
583            );
584
585            lens.lerp(target, 1.);
586        }
587        assert!(transform
588            .translation
589            .abs_diff_eq(Vec3::new(1., 2., -4.), 1e-5));
590        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
591        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
592
593        {
594            let mut added = Tick::new(0);
595            let mut last_changed = Tick::new(0);
596            let mut caller = MaybeLocation::caller();
597            let target = Mut::new(
598                &mut transform,
599                &mut added,
600                &mut last_changed,
601                Tick::new(0),
602                Tick::new(0),
603                caller.as_mut(),
604            );
605
606            lens.lerp(target, 0.3);
607        }
608        assert!(transform
609            .translation
610            .abs_diff_eq(Vec3::new(0.3, 0.6, -1.2), 1e-5));
611        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
612        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
613    }
614
615    #[test]
616    fn transform_rotation() {
617        let mut lens = TransformRotationLens {
618            start: Quat::IDENTITY,
619            end: Quat::from_rotation_z(100_f32.to_radians()),
620        };
621        let mut transform = Transform::default();
622
623        {
624            let mut added = Tick::new(0);
625            let mut last_changed = Tick::new(0);
626            let mut caller = MaybeLocation::caller();
627            let target = Mut::new(
628                &mut transform,
629                &mut added,
630                &mut last_changed,
631                Tick::new(0),
632                Tick::new(0),
633                caller.as_mut(),
634            );
635
636            lens.lerp(target, 0.);
637        }
638        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
639        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
640        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
641
642        {
643            let mut added = Tick::new(0);
644            let mut last_changed = Tick::new(0);
645            let mut caller = MaybeLocation::caller();
646            let target = Mut::new(
647                &mut transform,
648                &mut added,
649                &mut last_changed,
650                Tick::new(0),
651                Tick::new(0),
652                caller.as_mut(),
653            );
654
655            lens.lerp(target, 1.);
656        }
657        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
658        assert!(transform
659            .rotation
660            .abs_diff_eq(Quat::from_rotation_z(100_f32.to_radians()), 1e-5));
661        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
662
663        {
664            let mut added = Tick::new(0);
665            let mut last_changed = Tick::new(0);
666            let mut caller = MaybeLocation::caller();
667            let target = Mut::new(
668                &mut transform,
669                &mut added,
670                &mut last_changed,
671                Tick::new(0),
672                Tick::new(0),
673                caller.as_mut(),
674            );
675
676            lens.lerp(target, 0.3);
677        }
678        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
679        assert!(transform
680            .rotation
681            .abs_diff_eq(Quat::from_rotation_z(30_f32.to_radians()), 1e-5));
682        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
683    }
684
685    #[test]
686    fn transform_rotate_x() {
687        let mut lens = TransformRotateXLens {
688            start: 0.,
689            end: 1440_f32.to_radians(), // 4 turns
690        };
691        let mut transform = Transform::default();
692
693        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
694            {
695                let mut added = Tick::new(0);
696                let mut last_changed = Tick::new(0);
697                let mut caller = MaybeLocation::caller();
698                let target = Mut::new(
699                    &mut transform,
700                    &mut added,
701                    &mut last_changed,
702                    Tick::new(0),
703                    Tick::new(0),
704                    caller.as_mut(),
705                );
706
707                lens.lerp(target, *ratio);
708            }
709            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
710            if index == 1 || index == 3 {
711                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
712                // terms of rotation to the IDENTITY one, but numerically the w component is not
713                // the same so would fail an equality test.
714                assert!(transform
715                    .rotation
716                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
717            } else {
718                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
719            }
720            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
721        }
722
723        {
724            let mut added = Tick::new(0);
725            let mut last_changed = Tick::new(0);
726            let mut caller = MaybeLocation::caller();
727            let target = Mut::new(
728                &mut transform,
729                &mut added,
730                &mut last_changed,
731                Tick::new(0),
732                Tick::new(0),
733                caller.as_mut(),
734            );
735
736            lens.lerp(target, 0.1);
737        }
738        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
739        assert!(transform
740            .rotation
741            .abs_diff_eq(Quat::from_rotation_x(0.1 * (4. * TAU)), 1e-5));
742        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
743    }
744
745    #[test]
746    fn transform_rotate_y() {
747        let mut lens = TransformRotateYLens {
748            start: 0.,
749            end: 1440_f32.to_radians(), // 4 turns
750        };
751        let mut transform = Transform::default();
752
753        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
754            {
755                let mut added = Tick::new(0);
756                let mut last_changed = Tick::new(0);
757                let mut caller = MaybeLocation::caller();
758                let target = Mut::new(
759                    &mut transform,
760                    &mut added,
761                    &mut last_changed,
762                    Tick::new(0),
763                    Tick::new(0),
764                    caller.as_mut(),
765                );
766
767                lens.lerp(target, *ratio);
768            }
769            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
770            if index == 1 || index == 3 {
771                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
772                // terms of rotation to the IDENTITY one, but numerically the w component is not
773                // the same so would fail an equality test.
774                assert!(transform
775                    .rotation
776                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
777            } else {
778                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
779            }
780            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
781        }
782
783        {
784            let mut added = Tick::new(0);
785            let mut last_changed = Tick::new(0);
786            let mut caller = MaybeLocation::caller();
787            let target = Mut::new(
788                &mut transform,
789                &mut added,
790                &mut last_changed,
791                Tick::new(0),
792                Tick::new(0),
793                caller.as_mut(),
794            );
795
796            lens.lerp(target, 0.1);
797        }
798        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
799        assert!(transform
800            .rotation
801            .abs_diff_eq(Quat::from_rotation_y(0.1 * (4. * TAU)), 1e-5));
802        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
803    }
804
805    #[test]
806    fn transform_rotate_z() {
807        let mut lens = TransformRotateZLens {
808            start: 0.,
809            end: 1440_f32.to_radians(), // 4 turns
810        };
811        let mut transform = Transform::default();
812
813        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
814            {
815                let mut added = Tick::new(0);
816                let mut last_changed = Tick::new(0);
817                let mut caller = MaybeLocation::caller();
818                let target = Mut::new(
819                    &mut transform,
820                    &mut added,
821                    &mut last_changed,
822                    Tick::new(0),
823                    Tick::new(0),
824                    caller.as_mut(),
825                );
826
827                lens.lerp(target, *ratio);
828            }
829            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
830            if index == 1 || index == 3 {
831                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
832                // terms of rotation to the IDENTITY one, but numerically the w component is not
833                // the same so would fail an equality test.
834                assert!(transform
835                    .rotation
836                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
837            } else {
838                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
839            }
840            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
841        }
842
843        {
844            let mut added = Tick::new(0);
845            let mut last_changed = Tick::new(0);
846            let mut caller = MaybeLocation::caller();
847            let target = Mut::new(
848                &mut transform,
849                &mut added,
850                &mut last_changed,
851                Tick::new(0),
852                Tick::new(0),
853                caller.as_mut(),
854            );
855
856            lens.lerp(target, 0.1);
857        }
858        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
859        assert!(transform
860            .rotation
861            .abs_diff_eq(Quat::from_rotation_z(0.1 * (4. * TAU)), 1e-5));
862        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
863    }
864
865    #[test]
866    fn transform_rotate_axis() {
867        let axis = Vec3::ONE.normalize();
868        let mut lens = TransformRotateAxisLens {
869            axis,
870            start: 0.,
871            end: 1440_f32.to_radians(), // 4 turns
872        };
873        let mut transform = Transform::default();
874
875        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
876            {
877                let mut added = Tick::new(0);
878                let mut last_changed = Tick::new(0);
879                let mut caller = MaybeLocation::caller();
880                let target = Mut::new(
881                    &mut transform,
882                    &mut added,
883                    &mut last_changed,
884                    Tick::new(0),
885                    Tick::new(0),
886                    caller.as_mut(),
887                );
888
889                lens.lerp(target, *ratio);
890            }
891            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
892            if index == 1 || index == 3 {
893                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
894                // terms of rotation to the IDENTITY one, but numerically the w component is not
895                // the same so would fail an equality test.
896                assert!(transform
897                    .rotation
898                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
899            } else {
900                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
901            }
902            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
903        }
904
905        {
906            let mut added = Tick::new(0);
907            let mut last_changed = Tick::new(0);
908            let mut caller = MaybeLocation::caller();
909            let target = Mut::new(
910                &mut transform,
911                &mut added,
912                &mut last_changed,
913                Tick::new(0),
914                Tick::new(0),
915                caller.as_mut(),
916            );
917
918            lens.lerp(target, 0.1);
919        }
920        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
921        assert!(transform
922            .rotation
923            .abs_diff_eq(Quat::from_axis_angle(axis, 0.1 * (4. * TAU)), 1e-5));
924        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
925    }
926
927    #[test]
928    fn transform_scale() {
929        let mut lens = TransformScaleLens {
930            start: Vec3::ZERO,
931            end: Vec3::new(1., 2., -4.),
932        };
933        let mut transform = Transform::default();
934
935        {
936            let mut added = Tick::new(0);
937            let mut last_changed = Tick::new(0);
938            let mut caller = MaybeLocation::caller();
939            let target = Mut::new(
940                &mut transform,
941                &mut added,
942                &mut last_changed,
943                Tick::new(0),
944                Tick::new(0),
945                caller.as_mut(),
946            );
947
948            lens.lerp(target, 0.);
949        }
950        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
951        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
952        assert!(transform.scale.abs_diff_eq(Vec3::ZERO, 1e-5));
953
954        {
955            let mut added = Tick::new(0);
956            let mut last_changed = Tick::new(0);
957            let mut caller = MaybeLocation::caller();
958            let target = Mut::new(
959                &mut transform,
960                &mut added,
961                &mut last_changed,
962                Tick::new(0),
963                Tick::new(0),
964                caller.as_mut(),
965            );
966
967            lens.lerp(target, 1.);
968        }
969        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
970        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
971        assert!(transform.scale.abs_diff_eq(Vec3::new(1., 2., -4.), 1e-5));
972
973        {
974            let mut added = Tick::new(0);
975            let mut last_changed = Tick::new(0);
976            let mut caller = MaybeLocation::caller();
977            let target = Mut::new(
978                &mut transform,
979                &mut added,
980                &mut last_changed,
981                Tick::new(0),
982                Tick::new(0),
983                caller.as_mut(),
984            );
985
986            lens.lerp(target, 0.3);
987        }
988        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
989        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
990        assert!(transform.scale.abs_diff_eq(Vec3::new(0.3, 0.6, -1.2), 1e-5));
991    }
992
993    #[cfg(feature = "bevy_ui")]
994    #[test]
995    fn ui_position() {
996        let mut lens = UiPositionLens {
997            start: UiRect {
998                left: Val::Px(0.),
999                top: Val::Px(0.),
1000                right: Val::Auto,
1001                bottom: Val::Percent(25.),
1002            },
1003            end: UiRect {
1004                left: Val::Px(1.),
1005                top: Val::Px(5.),
1006                right: Val::Auto,
1007                bottom: Val::Percent(45.),
1008            },
1009        };
1010        let mut node = Node::default();
1011
1012        {
1013            let mut added = Tick::new(0);
1014            let mut last_changed = Tick::new(0);
1015            let mut caller = MaybeLocation::caller();
1016            let target = Mut::new(
1017                &mut node,
1018                &mut added,
1019                &mut last_changed,
1020                Tick::new(0),
1021                Tick::new(0),
1022                caller.as_mut(),
1023            );
1024
1025            lens.lerp(target, 0.);
1026        }
1027        assert_eq!(node.left, Val::Px(0.));
1028        assert_eq!(node.top, Val::Px(0.));
1029        assert_eq!(node.right, Val::Auto);
1030        assert_eq!(node.bottom, Val::Percent(25.));
1031
1032        {
1033            let mut added = Tick::new(0);
1034            let mut last_changed = Tick::new(0);
1035            let mut caller = MaybeLocation::caller();
1036            let target = Mut::new(
1037                &mut node,
1038                &mut added,
1039                &mut last_changed,
1040                Tick::new(0),
1041                Tick::new(0),
1042                caller.as_mut(),
1043            );
1044
1045            lens.lerp(target, 1.);
1046        }
1047        assert_eq!(node.left, Val::Px(1.));
1048        assert_eq!(node.top, Val::Px(5.));
1049        assert_eq!(node.right, Val::Auto);
1050        assert_eq!(node.bottom, Val::Percent(45.));
1051
1052        {
1053            let mut added = Tick::new(0);
1054            let mut last_changed = Tick::new(0);
1055            let mut caller = MaybeLocation::caller();
1056            let target = Mut::new(
1057                &mut node,
1058                &mut added,
1059                &mut last_changed,
1060                Tick::new(0),
1061                Tick::new(0),
1062                caller.as_mut(),
1063            );
1064
1065            lens.lerp(target, 0.3);
1066        }
1067        assert_eq!(node.left, Val::Px(0.3));
1068        assert_eq!(node.top, Val::Px(1.5));
1069        assert_eq!(node.right, Val::Auto);
1070        assert_eq!(node.bottom, Val::Percent(31.));
1071    }
1072
1073    #[cfg(feature = "bevy_sprite")]
1074    #[test]
1075    fn colormaterial_color() {
1076        let mut lens = ColorMaterialColorLens {
1077            start: RED.into(),
1078            end: BLUE.into(),
1079        };
1080        let mut assets = Assets::default();
1081        let handle = assets.add(ColorMaterial {
1082            color: Color::WHITE,
1083            texture: None,
1084            ..default()
1085        });
1086
1087        {
1088            let mut added = Tick::new(0);
1089            let mut last_changed = Tick::new(0);
1090            let mut caller = MaybeLocation::caller();
1091            let asset = assets.get_mut(handle.id()).unwrap();
1092            let target = Mut::new(
1093                asset,
1094                &mut added,
1095                &mut last_changed,
1096                Tick::new(0),
1097                Tick::new(0),
1098                caller.as_mut(),
1099            );
1100            lens.lerp(target, 0.);
1101        }
1102        assert_eq!(assets.get(handle.id()).unwrap().color, RED.into());
1103
1104        {
1105            let mut added = Tick::new(0);
1106            let mut last_changed = Tick::new(0);
1107            let mut caller = MaybeLocation::caller();
1108            let asset = assets.get_mut(handle.id()).unwrap();
1109            let target = Mut::new(
1110                asset,
1111                &mut added,
1112                &mut last_changed,
1113                Tick::new(0),
1114                Tick::new(0),
1115                caller.as_mut(),
1116            );
1117            lens.lerp(target, 1.);
1118        }
1119        assert_eq!(assets.get(handle.id()).unwrap().color, BLUE.into());
1120
1121        {
1122            let mut added = Tick::new(0);
1123            let mut last_changed = Tick::new(0);
1124            let mut caller = MaybeLocation::caller();
1125            let asset = assets.get_mut(handle.id()).unwrap();
1126            let target = Mut::new(
1127                asset,
1128                &mut added,
1129                &mut last_changed,
1130                Tick::new(0),
1131                Tick::new(0),
1132                caller.as_mut(),
1133            );
1134            lens.lerp(target, 0.3);
1135        }
1136        assert_eq!(
1137            assets.get(handle.id()).unwrap().color,
1138            Color::srgba(0.7, 0., 0.3, 1.0)
1139        );
1140    }
1141
1142    #[cfg(feature = "bevy_sprite")]
1143    #[test]
1144    fn sprite_color() {
1145        let mut lens = SpriteColorLens {
1146            start: RED.into(),
1147            end: BLUE.into(),
1148        };
1149        let mut sprite = Sprite {
1150            color: Color::WHITE,
1151            ..default()
1152        };
1153
1154        {
1155            let mut added = Tick::new(0);
1156            let mut last_changed = Tick::new(0);
1157            let mut caller = MaybeLocation::caller();
1158            let target = Mut::new(
1159                &mut sprite,
1160                &mut added,
1161                &mut last_changed,
1162                Tick::new(0),
1163                Tick::new(0),
1164                caller.as_mut(),
1165            );
1166
1167            lens.lerp(target, 0.);
1168        }
1169        assert_eq!(sprite.color, RED.into());
1170
1171        {
1172            let mut added = Tick::new(0);
1173            let mut last_changed = Tick::new(0);
1174            let mut caller = MaybeLocation::caller();
1175            let target = Mut::new(
1176                &mut sprite,
1177                &mut added,
1178                &mut last_changed,
1179                Tick::new(0),
1180                Tick::new(0),
1181                caller.as_mut(),
1182            );
1183
1184            lens.lerp(target, 1.);
1185        }
1186        assert_eq!(sprite.color, BLUE.into());
1187
1188        {
1189            let mut added = Tick::new(0);
1190            let mut last_changed = Tick::new(0);
1191            let mut caller = MaybeLocation::caller();
1192            let target = Mut::new(
1193                &mut sprite,
1194                &mut added,
1195                &mut last_changed,
1196                Tick::new(0),
1197                Tick::new(0),
1198                caller.as_mut(),
1199            );
1200
1201            lens.lerp(target, 0.3);
1202        }
1203        assert_eq!(sprite.color, Color::srgba(0.7, 0., 0.3, 1.0));
1204    }
1205}