• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

djeedai / bevy_tweening / 7739125011

01 Feb 2024 09:36AM UTC coverage: 92.057% (-0.2%) from 92.248%
7739125011

Pull #117

github

web-flow
Merge a0b964e77 into 5e6214b70
Pull Request #117: Updates for Bevy 0.13

13 of 16 new or added lines in 2 files covered. (81.25%)

7 existing lines in 1 file now uncovered.

1298 of 1410 relevant lines covered (92.06%)

1.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.62
/src/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.12.0/bevy/transform/components/struct.Transform.html#structfield.rotation
35
//! [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
36
//! [`Quat::slerp()`]: https://docs.rs/bevy/0.12.0/bevy/math/struct.Quat.html#method.slerp
37

38
use crate::color_vec4_ext::*;
39
use bevy::prelude::*;
40

41
/// A lens over a subset of a component.
42
///
43
/// The lens takes a `target` component or asset from a query, as a mutable
44
/// reference, and animates (tweens) a subset of the fields of the
45
/// component/asset based on the linear ratio `ratio` in \[0:1\], already
46
/// sampled from the easing curve.
47
///
48
/// # Example
49
///
50
/// Implement `Lens` for a custom type:
51
///
52
/// ```rust
53
/// # use bevy::prelude::*;
54
/// # use bevy_tweening::*;
55
/// struct MyLens {
56
///   start: f32,
57
///   end: f32,
58
/// }
59
///
60
/// #[derive(Component)]
61
/// struct MyStruct(f32);
62
///
63
/// impl Lens<MyStruct> for MyLens {
64
///   fn lerp(&mut self, target: &mut MyStruct, ratio: f32) {
65
///     target.0 = self.start + (self.end - self.start) * ratio;
66
///   }
67
/// }
68
/// ```
69
pub trait Lens<T> {
70
    /// Perform a linear interpolation (lerp) over the subset of fields of a
71
    /// component or asset the lens focuses on, based on the linear ratio
72
    /// `ratio`. The `target` component or asset is mutated in place. The
73
    /// implementation decides which fields are interpolated, and performs
74
    /// the animation in-place, overwriting the target.
75
    fn lerp(&mut self, target: &mut T, ratio: f32);
76
}
77

78
/// A lens to manipulate the [`color`] field of a section of a [`Text`]
79
/// component.
80
///
81
/// [`color`]: https://docs.rs/bevy/0.12.0/bevy/text/struct.TextStyle.html#structfield.color
82
/// [`Text`]: https://docs.rs/bevy/0.12.0/bevy/text/struct.Text.html
83
#[cfg(feature = "bevy_text")]
84
#[derive(Debug, Copy, Clone, PartialEq)]
85
pub struct TextColorLens {
86
    /// Start color.
87
    pub start: Color,
88
    /// End color.
89
    pub end: Color,
90
    /// Index of the text section in the [`Text`] component.
91
    pub section: usize,
92
}
93

94
#[cfg(feature = "bevy_text")]
95
impl Lens<Text> for TextColorLens {
96
    fn lerp(&mut self, target: &mut Text, ratio: f32) {
1✔
97
        // Note: Add<f32> for Color affects alpha, but not Mul<f32>. So use Vec4 for
98
        // consistency.
99
        let start: Vec4 = self.start.to_vec();
1✔
100
        let end: Vec4 = self.end.to_vec();
1✔
101
        let value = start.lerp(end, ratio);
1✔
102

103
        if let Some(section) = target.sections.get_mut(self.section) {
1✔
104
            section.style.color = value.to_color();
1✔
105
        }
106
    }
107
}
108

109
/// A lens to manipulate the [`translation`] field of a [`Transform`] component.
110
///
111
/// [`translation`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html#structfield.translation
112
/// [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
113
#[derive(Debug, Copy, Clone, PartialEq)]
114
pub struct TransformPositionLens {
115
    /// Start value of the translation.
116
    pub start: Vec3,
117
    /// End value of the translation.
118
    pub end: Vec3,
119
}
120

121
impl Lens<Transform> for TransformPositionLens {
122
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
1✔
123
        let value = self.start + (self.end - self.start) * ratio;
1✔
124
        target.translation = value;
1✔
125
    }
126
}
127

128
/// A lens to manipulate the [`rotation`] field of a [`Transform`] component.
129
///
130
/// This lens interpolates the [`rotation`] field of a [`Transform`] component
131
/// from a `start` value to an `end` value using the spherical linear
132
/// interpolation provided by [`Quat::slerp()`]. This means the rotation always
133
/// uses the shortest path from `start` to `end`. In particular, this means it
134
/// cannot make entities do a full 360 degrees turn. Instead use
135
/// [`TransformRotateXLens`] and similar to interpolate the rotation angle
136
/// around a given axis.
137
///
138
/// See the [top-level `lens` module documentation] for a comparison of rotation
139
/// lenses.
140
///
141
/// [`rotation`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html#structfield.rotation
142
/// [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
143
/// [`Quat::slerp()`]: https://docs.rs/bevy/0.12.0/bevy/math/struct.Quat.html#method.slerp
144
/// [top-level `lens` module documentation]: crate::lens
145
#[derive(Debug, Copy, Clone, PartialEq)]
146
pub struct TransformRotationLens {
147
    /// Start value of the rotation.
148
    pub start: Quat,
149
    /// End value of the rotation.
150
    pub end: Quat,
151
}
152

153
impl Lens<Transform> for TransformRotationLens {
154
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
1✔
155
        target.rotation = self.start.slerp(self.end, ratio);
1✔
156
    }
157
}
158

159
/// A lens to rotate a [`Transform`] component around its local X axis.
160
///
161
/// This lens interpolates the rotation angle of a [`Transform`] component from
162
/// a `start` value to an `end` value, for a rotation around the X axis. Unlike
163
/// [`TransformRotationLens`], it can produce an animation that rotates the
164
/// entity any number of turns around its local X axis.
165
///
166
/// See the [top-level `lens` module documentation] for a comparison of rotation
167
/// lenses.
168
///
169
/// [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
170
/// [top-level `lens` module documentation]: crate::lens
171
#[derive(Debug, Copy, Clone, PartialEq)]
172
pub struct TransformRotateXLens {
173
    /// Start value of the rotation angle, in radians.
174
    pub start: f32,
175
    /// End value of the rotation angle, in radians.
176
    pub end: f32,
177
}
178

179
impl Lens<Transform> for TransformRotateXLens {
180
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
1✔
181
        let angle = (self.end - self.start).mul_add(ratio, self.start);
1✔
182
        target.rotation = Quat::from_rotation_x(angle);
1✔
183
    }
184
}
185

186
/// A lens to rotate a [`Transform`] component around its local Y axis.
187
///
188
/// This lens interpolates the rotation angle of a [`Transform`] component from
189
/// a `start` value to an `end` value, for a rotation around the Y axis. Unlike
190
/// [`TransformRotationLens`], it can produce an animation that rotates the
191
/// entity any number of turns around its local Y axis.
192
///
193
/// See the [top-level `lens` module documentation] for a comparison of rotation
194
/// lenses.
195
///
196
/// [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
197
/// [top-level `lens` module documentation]: crate::lens
198
#[derive(Debug, Copy, Clone, PartialEq)]
199
pub struct TransformRotateYLens {
200
    /// Start value of the rotation angle, in radians.
201
    pub start: f32,
202
    /// End value of the rotation angle, in radians.
203
    pub end: f32,
204
}
205

206
impl Lens<Transform> for TransformRotateYLens {
207
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
1✔
208
        let angle = (self.end - self.start).mul_add(ratio, self.start);
1✔
209
        target.rotation = Quat::from_rotation_y(angle);
1✔
210
    }
211
}
212

213
/// A lens to rotate a [`Transform`] component around its local Z axis.
214
///
215
/// This lens interpolates the rotation angle of a [`Transform`] component from
216
/// a `start` value to an `end` value, for a rotation around the Z axis. Unlike
217
/// [`TransformRotationLens`], it can produce an animation that rotates the
218
/// entity any number of turns around its local Z axis.
219
///
220
/// See the [top-level `lens` module documentation] for a comparison of rotation
221
/// lenses.
222
///
223
/// [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
224
/// [top-level `lens` module documentation]: crate::lens
225
#[derive(Debug, Copy, Clone, PartialEq)]
226
pub struct TransformRotateZLens {
227
    /// Start value of the rotation angle, in radians.
228
    pub start: f32,
229
    /// End value of the rotation angle, in radians.
230
    pub end: f32,
231
}
232

233
impl Lens<Transform> for TransformRotateZLens {
234
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
1✔
235
        let angle = (self.end - self.start).mul_add(ratio, self.start);
1✔
236
        target.rotation = Quat::from_rotation_z(angle);
1✔
237
    }
238
}
239

240
/// A lens to rotate a [`Transform`] component around a given fixed axis.
241
///
242
/// This lens interpolates the rotation angle of a [`Transform`] component from
243
/// a `start` value to an `end` value, for a rotation around a given axis.
244
/// Unlike [`TransformRotationLens`], it can produce an animation that rotates
245
/// the entity any number of turns around that axis.
246
///
247
/// See the [top-level `lens` module documentation] for a comparison of rotation
248
/// lenses.
249
///
250
/// # Panics
251
///
252
/// This method panics if the `axis` vector is not normalized.
253
///
254
/// [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
255
/// [top-level `lens` module documentation]: crate::lens
256
#[derive(Debug, Copy, Clone, PartialEq)]
257
pub struct TransformRotateAxisLens {
258
    /// The normalized rotation axis.
259
    pub axis: Vec3,
260
    /// Start value of the rotation angle, in radians.
261
    pub start: f32,
262
    /// End value of the rotation angle, in radians.
263
    pub end: f32,
264
}
265

266
impl Lens<Transform> for TransformRotateAxisLens {
267
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
1✔
268
        let angle = (self.end - self.start).mul_add(ratio, self.start);
1✔
269
        target.rotation = Quat::from_axis_angle(self.axis, angle);
1✔
270
    }
271
}
272

273
/// A lens to manipulate the [`scale`] field of a [`Transform`] component.
274
///
275
/// [`scale`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html#structfield.scale
276
/// [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
277
#[derive(Debug, Copy, Clone, PartialEq)]
278
pub struct TransformScaleLens {
279
    /// Start value of the scale.
280
    pub start: Vec3,
281
    /// End value of the scale.
282
    pub end: Vec3,
283
}
284

285
impl Lens<Transform> for TransformScaleLens {
286
    fn lerp(&mut self, target: &mut Transform, ratio: f32) {
1✔
287
        let value = self.start + (self.end - self.start) * ratio;
1✔
288
        target.scale = value;
1✔
289
    }
290
}
291

292
/// A lens to manipulate the [`position`] field of a UI [`Style`] component.
293
///
294
/// [`position`]: https://docs.rs/bevy/0.12.0/bevy/ui/struct.Style.html#structfield.position
295
/// [`Style`]: https://docs.rs/bevy/0.12.0/bevy/ui/struct.Style.html
296
#[cfg(feature = "bevy_ui")]
297
#[derive(Debug, Copy, Clone, PartialEq)]
298
pub struct UiPositionLens {
299
    /// Start position.
300
    pub start: UiRect,
301
    /// End position.
302
    pub end: UiRect,
303
}
304

305
#[cfg(feature = "bevy_ui")]
306
fn lerp_val(start: &Val, end: &Val, ratio: f32) -> Val {
1✔
307
    match (start, end) {
2✔
308
        (Val::Percent(start), Val::Percent(end)) => {
1✔
309
            Val::Percent((end - start).mul_add(ratio, *start))
1✔
310
        }
311
        (Val::Px(start), Val::Px(end)) => Val::Px((end - start).mul_add(ratio, *start)),
1✔
312
        _ => *start,
1✔
313
    }
314
}
315

316
#[cfg(feature = "bevy_ui")]
317
impl Lens<Style> for UiPositionLens {
318
    fn lerp(&mut self, target: &mut Style, ratio: f32) {
1✔
319
        target.left = lerp_val(&self.start.left, &self.end.left, ratio);
1✔
320
        target.right = lerp_val(&self.start.right, &self.end.right, ratio);
1✔
321
        target.top = lerp_val(&self.start.top, &self.end.top, ratio);
1✔
322
        target.bottom = lerp_val(&self.start.bottom, &self.end.bottom, ratio);
1✔
323
    }
324
}
325

326
/// Gamer
327
#[cfg(feature = "bevy_ui")]
328
#[derive(Debug, Copy, Clone, PartialEq)]
329
pub struct UiBackgroundColorLens {
330
    /// Start position.
331
    pub start: Color,
332
    /// End position.
333
    pub end: Color,
334
}
335

336
#[cfg(feature = "bevy_ui")]
337
impl Lens<BackgroundColor> for UiBackgroundColorLens {
338
    fn lerp(&mut self, target: &mut BackgroundColor, ratio: f32) {
×
NEW
339
        let start: Vec4 = self.start.to_vec();
×
NEW
340
        let end: Vec4 = self.end.to_vec();
×
341
        let value = start.lerp(end, ratio);
×
NEW
342
        target.0 = value.to_color();
×
343
    }
344
}
345

346
/// A lens to manipulate the [`color`] field of a [`ColorMaterial`] asset.
347
///
348
/// [`color`]: https://docs.rs/bevy/0.12.0/bevy/sprite/struct.ColorMaterial.html#structfield.color
349
/// [`ColorMaterial`]: https://docs.rs/bevy/0.12.0/bevy/sprite/struct.ColorMaterial.html
350
#[cfg(feature = "bevy_sprite")]
351
#[derive(Debug, Copy, Clone, PartialEq)]
352
pub struct ColorMaterialColorLens {
353
    /// Start color.
354
    pub start: Color,
355
    /// End color.
356
    pub end: Color,
357
}
358

359
#[cfg(feature = "bevy_sprite")]
360
impl Lens<ColorMaterial> for ColorMaterialColorLens {
361
    fn lerp(&mut self, target: &mut ColorMaterial, ratio: f32) {
1✔
362
        // Note: Add<f32> for Color affects alpha, but not Mul<f32>. So use Vec4 for
363
        // consistency.
364
        let start: Vec4 = self.start.to_vec();
1✔
365
        let end: Vec4 = self.end.to_vec();
1✔
366
        let value = start.lerp(end, ratio);
1✔
367
        target.color = value.to_color();
1✔
368
    }
369
}
370

371
/// A lens to manipulate the [`color`] field of a [`Sprite`] asset.
372
///
373
/// [`color`]: https://docs.rs/bevy/0.12.0/bevy/sprite/struct.Sprite.html#structfield.color
374
/// [`Sprite`]: https://docs.rs/bevy/0.12.0/bevy/sprite/struct.Sprite.html
375
#[cfg(feature = "bevy_sprite")]
376
#[derive(Debug, Copy, Clone, PartialEq)]
377
pub struct SpriteColorLens {
378
    /// Start color.
379
    pub start: Color,
380
    /// End color.
381
    pub end: Color,
382
}
383

384
#[cfg(feature = "bevy_sprite")]
385
impl Lens<Sprite> for SpriteColorLens {
386
    fn lerp(&mut self, target: &mut Sprite, ratio: f32) {
1✔
387
        // Note: Add<f32> for Color affects alpha, but not Mul<f32>. So use Vec4 for
388
        // consistency.
389
        let start: Vec4 = self.start.to_vec();
1✔
390
        let end: Vec4 = self.end.to_vec();
1✔
391
        let value = start.lerp(end, ratio);
1✔
392
        target.color = value.to_color();
1✔
393
    }
394
}
395

396
#[cfg(test)]
397
mod tests {
398
    use std::f32::consts::TAU;
399

400
    use super::*;
401

402
    #[cfg(feature = "bevy_text")]
403
    #[test]
404
    fn text_color() {
3✔
405
        let mut lens = TextColorLens {
406
            start: Color::RED,
407
            end: Color::BLUE,
408
            section: 0,
409
        };
410
        let mut text = Text::from_section("", default());
1✔
411

412
        lens.lerp(&mut text, 0.);
1✔
413
        assert_eq!(text.sections[0].style.color, Color::RED);
1✔
414

415
        lens.lerp(&mut text, 1.);
1✔
416
        assert_eq!(text.sections[0].style.color, Color::BLUE);
1✔
417

418
        lens.lerp(&mut text, 0.3);
1✔
419
        assert_eq!(text.sections[0].style.color, Color::rgba(0.7, 0., 0.3, 1.0));
1✔
420

421
        let mut lens_section1 = TextColorLens {
422
            start: Color::RED,
423
            end: Color::BLUE,
424
            section: 1,
425
        };
426

427
        lens_section1.lerp(&mut text, 1.);
1✔
428
        // Should not have changed because the lens targets section 1
429
        assert_eq!(text.sections[0].style.color, Color::rgba(0.7, 0., 0.3, 1.0));
1✔
430

431
        text.sections.push(TextSection {
1✔
432
            value: "".to_string(),
1✔
433
            style: Default::default(),
1✔
434
        });
435

436
        lens_section1.lerp(&mut text, 0.3);
1✔
437
        assert_eq!(text.sections[1].style.color, Color::rgba(0.7, 0., 0.3, 1.0));
1✔
438
    }
439

440
    #[test]
441
    fn transform_position() {
3✔
442
        let mut lens = TransformPositionLens {
443
            start: Vec3::ZERO,
444
            end: Vec3::new(1., 2., -4.),
445
        };
446
        let mut transform = Transform::default();
1✔
447

448
        lens.lerp(&mut transform, 0.);
1✔
449
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
450
        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
451
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
452

453
        lens.lerp(&mut transform, 1.);
1✔
454
        assert!(transform
2✔
455
            .translation
456
            .abs_diff_eq(Vec3::new(1., 2., -4.), 1e-5));
457
        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
458
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
459

460
        lens.lerp(&mut transform, 0.3);
1✔
461
        assert!(transform
2✔
462
            .translation
463
            .abs_diff_eq(Vec3::new(0.3, 0.6, -1.2), 1e-5));
464
        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
465
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
466
    }
467

468
    #[test]
469
    fn transform_rotation() {
3✔
470
        let mut lens = TransformRotationLens {
471
            start: Quat::IDENTITY,
472
            end: Quat::from_rotation_z(100_f32.to_radians()),
1✔
473
        };
474
        let mut transform = Transform::default();
1✔
475

476
        lens.lerp(&mut transform, 0.);
1✔
477
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
478
        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
479
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
480

481
        lens.lerp(&mut transform, 1.);
1✔
482
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
483
        assert!(transform
2✔
484
            .rotation
485
            .abs_diff_eq(Quat::from_rotation_z(100_f32.to_radians()), 1e-5));
1✔
486
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
487

488
        lens.lerp(&mut transform, 0.3);
1✔
489
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
490
        assert!(transform
2✔
491
            .rotation
492
            .abs_diff_eq(Quat::from_rotation_z(30_f32.to_radians()), 1e-5));
1✔
493
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
494
    }
495

496
    #[test]
497
    fn transform_rotate_x() {
3✔
498
        let mut lens = TransformRotateXLens {
499
            start: 0.,
500
            end: 1440_f32.to_radians(), // 4 turns
1✔
501
        };
502
        let mut transform = Transform::default();
1✔
503

504
        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
2✔
505
            lens.lerp(&mut transform, *ratio);
1✔
506
            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
507
            if index == 1 || index == 3 {
2✔
508
                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
509
                // terms of rotation to the IDENTITY one, but numerically the w component is not
510
                // the same so would fail an equality test.
511
                assert!(transform
2✔
512
                    .rotation
513
                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
1✔
514
            } else {
515
                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
516
            }
517
            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
518
        }
519

520
        lens.lerp(&mut transform, 0.1);
1✔
521
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
522
        assert!(transform
2✔
523
            .rotation
524
            .abs_diff_eq(Quat::from_rotation_x(0.1 * (4. * TAU)), 1e-5));
1✔
525
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
526
    }
527

528
    #[test]
529
    fn transform_rotate_y() {
3✔
530
        let mut lens = TransformRotateYLens {
531
            start: 0.,
532
            end: 1440_f32.to_radians(), // 4 turns
1✔
533
        };
534
        let mut transform = Transform::default();
1✔
535

536
        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
2✔
537
            lens.lerp(&mut transform, *ratio);
1✔
538
            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
539
            if index == 1 || index == 3 {
2✔
540
                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
541
                // terms of rotation to the IDENTITY one, but numerically the w component is not
542
                // the same so would fail an equality test.
543
                assert!(transform
2✔
544
                    .rotation
545
                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
1✔
546
            } else {
547
                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
548
            }
549
            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
550
        }
551

552
        lens.lerp(&mut transform, 0.1);
1✔
553
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
554
        assert!(transform
2✔
555
            .rotation
556
            .abs_diff_eq(Quat::from_rotation_y(0.1 * (4. * TAU)), 1e-5));
1✔
557
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
558
    }
559

560
    #[test]
561
    fn transform_rotate_z() {
3✔
562
        let mut lens = TransformRotateZLens {
563
            start: 0.,
564
            end: 1440_f32.to_radians(), // 4 turns
1✔
565
        };
566
        let mut transform = Transform::default();
1✔
567

568
        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
2✔
569
            lens.lerp(&mut transform, *ratio);
1✔
570
            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
571
            if index == 1 || index == 3 {
2✔
572
                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
573
                // terms of rotation to the IDENTITY one, but numerically the w component is not
574
                // the same so would fail an equality test.
575
                assert!(transform
2✔
576
                    .rotation
577
                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
1✔
578
            } else {
579
                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
580
            }
581
            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
582
        }
583

584
        lens.lerp(&mut transform, 0.1);
1✔
585
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
586
        assert!(transform
2✔
587
            .rotation
588
            .abs_diff_eq(Quat::from_rotation_z(0.1 * (4. * TAU)), 1e-5));
1✔
589
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
590
    }
591

592
    #[test]
593
    fn transform_rotate_axis() {
3✔
594
        let axis = Vec3::ONE.normalize();
1✔
595
        let mut lens = TransformRotateAxisLens {
596
            axis,
597
            start: 0.,
598
            end: 1440_f32.to_radians(), // 4 turns
1✔
599
        };
600
        let mut transform = Transform::default();
1✔
601

602
        for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
2✔
603
            lens.lerp(&mut transform, *ratio);
1✔
604
            assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
605
            if index == 1 || index == 3 {
2✔
606
                // For odd-numbered turns, the opposite Quat is produced. This is equivalent in
607
                // terms of rotation to the IDENTITY one, but numerically the w component is not
608
                // the same so would fail an equality test.
609
                assert!(transform
2✔
610
                    .rotation
611
                    .abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
1✔
612
            } else {
613
                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
614
            }
615
            assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
616
        }
617

618
        lens.lerp(&mut transform, 0.1);
1✔
619
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
620
        assert!(transform
2✔
621
            .rotation
622
            .abs_diff_eq(Quat::from_axis_angle(axis, 0.1 * (4. * TAU)), 1e-5));
1✔
623
        assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
1✔
624
    }
625

626
    #[test]
627
    fn transform_scale() {
3✔
628
        let mut lens = TransformScaleLens {
629
            start: Vec3::ZERO,
630
            end: Vec3::new(1., 2., -4.),
631
        };
632
        let mut transform = Transform::default();
1✔
633

634
        lens.lerp(&mut transform, 0.);
1✔
635
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
636
        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
637
        assert!(transform.scale.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
638

639
        lens.lerp(&mut transform, 1.);
1✔
640
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
641
        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
642
        assert!(transform.scale.abs_diff_eq(Vec3::new(1., 2., -4.), 1e-5));
2✔
643

644
        lens.lerp(&mut transform, 0.3);
1✔
645
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
1✔
646
        assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1✔
647
        assert!(transform.scale.abs_diff_eq(Vec3::new(0.3, 0.6, -1.2), 1e-5));
2✔
648
    }
649

650
    #[cfg(feature = "bevy_ui")]
651
    #[test]
652
    fn ui_position() {
3✔
653
        let mut lens = UiPositionLens {
654
            start: UiRect {
1✔
655
                left: Val::Px(0.),
656
                top: Val::Px(0.),
657
                right: Val::Auto,
658
                bottom: Val::Percent(25.),
659
            },
660
            end: UiRect {
1✔
661
                left: Val::Px(1.),
662
                top: Val::Px(5.),
663
                right: Val::Auto,
664
                bottom: Val::Percent(45.),
665
            },
666
        };
667
        let mut style = Style::default();
1✔
668

669
        lens.lerp(&mut style, 0.);
1✔
670
        assert_eq!(style.left, Val::Px(0.));
1✔
671
        assert_eq!(style.top, Val::Px(0.));
1✔
672
        assert_eq!(style.right, Val::Auto);
1✔
673
        assert_eq!(style.bottom, Val::Percent(25.));
1✔
674

675
        lens.lerp(&mut style, 1.);
1✔
676
        assert_eq!(style.left, Val::Px(1.));
1✔
677
        assert_eq!(style.top, Val::Px(5.));
1✔
678
        assert_eq!(style.right, Val::Auto);
1✔
679
        assert_eq!(style.bottom, Val::Percent(45.));
1✔
680

681
        lens.lerp(&mut style, 0.3);
1✔
682
        assert_eq!(style.left, Val::Px(0.3));
1✔
683
        assert_eq!(style.top, Val::Px(1.5));
1✔
684
        assert_eq!(style.right, Val::Auto);
1✔
685
        assert_eq!(style.bottom, Val::Percent(31.));
1✔
686
    }
687

688
    #[cfg(feature = "bevy_sprite")]
689
    #[test]
690
    fn colormaterial_color() {
3✔
691
        let mut lens = ColorMaterialColorLens {
692
            start: Color::RED,
693
            end: Color::BLUE,
694
        };
695
        let mut mat = ColorMaterial {
696
            color: Color::WHITE,
697
            texture: None,
698
        };
699

700
        lens.lerp(&mut mat, 0.);
1✔
701
        assert_eq!(mat.color, Color::RED);
1✔
702

703
        lens.lerp(&mut mat, 1.);
1✔
704
        assert_eq!(mat.color, Color::BLUE);
1✔
705

706
        lens.lerp(&mut mat, 0.3);
1✔
707
        assert_eq!(mat.color, Color::rgba(0.7, 0., 0.3, 1.0));
1✔
708
    }
709

710
    #[cfg(feature = "bevy_sprite")]
711
    #[test]
712
    fn sprite_color() {
3✔
713
        let mut lens = SpriteColorLens {
714
            start: Color::RED,
715
            end: Color::BLUE,
716
        };
717
        let mut sprite = Sprite {
718
            color: Color::WHITE,
719
            ..default()
720
        };
721

722
        lens.lerp(&mut sprite, 0.);
1✔
723
        assert_eq!(sprite.color, Color::RED);
1✔
724

725
        lens.lerp(&mut sprite, 1.);
1✔
726
        assert_eq!(sprite.color, Color::BLUE);
1✔
727

728
        lens.lerp(&mut sprite, 0.3);
1✔
729
        assert_eq!(sprite.color, Color::rgba(0.7, 0., 0.3, 1.0));
1✔
730
    }
731
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc