Skip to content
Snippets Groups Projects
Unverified Commit a7a677e2 authored by Jerome Humbert's avatar Jerome Humbert Committed by GitHub
Browse files

Add and document various rotation lenses (#6)

Add more predefined rotation lenses interpolating the angle of rotation
instead of the `Quat` itself directly. Document at the `lens` module
level the difference between the shortest-path lens and the
angle-focused ones.

Bug: #5
parent df92de1d
No related branches found
No related tags found
No related merge requests found
//! Collection of predefined lenses for common Bevy components and assets.
//!
//! # Predefined lenses
//!
//! This module contains predefined lenses for common use cases. Those lenses are
//! entirely optional. They can be used if they fit your use case, to save some time,
//! but are not treated any differently from a custom user-provided lens.
//!
//! # Rotations
//!
//! Several rotation lenses are provided, with different properties.
//!
//! ## Shortest-path rotation
//!
//! The [`TransformRotationLens`] animates the [`rotation`] field of a [`Transform`]
//! component using [`Quat::slerp()`]. It inherits the properties of that method, and
//! in particular the fact it always finds the "shortest path" from start to end. This
//! is well suited for animating a rotation between two given directions, but will
//! provide unexpected results if you try to make an entity rotate around a given axis
//! for more than half a turn, as [`Quat::slerp()`] will then try to move "the other
//! way around".
//!
//! ## Angle-focused rotations
//!
//! Conversely, for cases where the rotation direction is important, like when trying
//! to do a full 360-degree turn, a series of angle-based interpolation lenses is
//! provided:
//! - [`TransformRotateXLens`]
//! - [`TransformRotateYLens`]
//! - [`TransformRotateZLens`]
//! - [`TransformRotateAxisLens`]
//!
//! [`rotation`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html#structfield.rotation
//! [`Transform`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html
//! [`Quat::slerp()`]: https://docs.rs/bevy/0.6.0/bevy/math/struct.Quat.html#method.slerp
use bevy::prelude::*;
/// A lens over a subset of a component.
///
/// The lens takes a `target` component or asset from a query, as a mutable reference,
/// and animates (tweens) a subet of the fields of the component/asset based on the
/// linear ratio `ratio`, already sampled from the easing curve.
/// and animates (tweens) a subset of the fields of the component/asset based on the
/// linear ratio `ratio` in \[0:1\], already sampled from the easing curve.
///
/// # Example
///
......@@ -83,8 +117,19 @@ impl Lens<Transform> for TransformPositionLens {
/// A lens to manipulate the [`rotation`] field of a [`Transform`] component.
///
/// This lens interpolates the [`rotation`] field of a [`Transform`] component
/// from a `start` value to an `end` value using the spherical linear interpolation
/// provided by [`Quat::slerp()`]. This means the rotation always uses the shortest
/// path from `start` to `end`. In particular, this means it cannot make entities
/// do a full 360 degrees turn. Instead use [`TransformRotateXLens`] and similar
/// to interpolate the rotation angle around a given axis.
///
/// See the [top-level `lens` module documentation] for a comparison of rotation lenses.
///
/// [`rotation`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html#structfield.rotation
/// [`Transform`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html
/// [`Quat::slerp()`]: https://docs.rs/bevy/0.6.0/bevy/math/struct.Quat.html#method.slerp
/// [top-level `lens` module documentation]: crate::lens
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TransformRotationLens {
/// Start value of the rotation.
......@@ -95,7 +140,117 @@ pub struct TransformRotationLens {
impl Lens<Transform> for TransformRotationLens {
fn lerp(&mut self, target: &mut Transform, ratio: f32) {
target.rotation = self.start.slerp(self.end, ratio); // FIXME - This slerps the shortest path only! https://docs.rs/bevy/latest/bevy/math/struct.Quat.html#method.slerp
target.rotation = self.start.slerp(self.end, ratio);
}
}
/// A lens to rotate a [`Transform`] component around its local X axis.
///
/// This lens interpolates the rotation angle of a [`Transform`] component from
/// a `start` value to an `end` value, for a rotation around the X axis. Unlike
/// [`TransformRotationLens`], it can produce an animation that rotates the entity
/// any number of turns around its local X axis.
///
/// See the [top-level `lens` module documentation] for a comparison of rotation lenses.
///
/// [`Transform`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html
/// [top-level `lens` module documentation]: crate::lens
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TransformRotateXLens {
/// Start value of the rotation angle, in radians.
pub start: f32,
/// End value of the rotation angle, in radians.
pub end: f32,
}
impl Lens<Transform> for TransformRotateXLens {
fn lerp(&mut self, target: &mut Transform, ratio: f32) {
let angle = self.start + (self.end - self.start) * ratio;
target.rotation = Quat::from_rotation_x(angle);
}
}
/// A lens to rotate a [`Transform`] component around its local Y axis.
///
/// This lens interpolates the rotation angle of a [`Transform`] component from
/// a `start` value to an `end` value, for a rotation around the Y axis. Unlike
/// [`TransformRotationLens`], it can produce an animation that rotates the entity
/// any number of turns around its local Y axis.
///
/// See the [top-level `lens` module documentation] for a comparison of rotation lenses.
///
/// [`Transform`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html
/// [top-level `lens` module documentation]: crate::lens
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TransformRotateYLens {
/// Start value of the rotation angle, in radians.
pub start: f32,
/// End value of the rotation angle, in radians.
pub end: f32,
}
impl Lens<Transform> for TransformRotateYLens {
fn lerp(&mut self, target: &mut Transform, ratio: f32) {
let angle = self.start + (self.end - self.start) * ratio;
target.rotation = Quat::from_rotation_y(angle);
}
}
/// A lens to rotate a [`Transform`] component around its local Z axis.
///
/// This lens interpolates the rotation angle of a [`Transform`] component from
/// a `start` value to an `end` value, for a rotation around the Z axis. Unlike
/// [`TransformRotationLens`], it can produce an animation that rotates the entity
/// any number of turns around its local Z axis.
///
/// See the [top-level `lens` module documentation] for a comparison of rotation lenses.
///
/// [`Transform`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html
/// [top-level `lens` module documentation]: crate::lens
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TransformRotateZLens {
/// Start value of the rotation angle, in radians.
pub start: f32,
/// End value of the rotation angle, in radians.
pub end: f32,
}
impl Lens<Transform> for TransformRotateZLens {
fn lerp(&mut self, target: &mut Transform, ratio: f32) {
let angle = self.start + (self.end - self.start) * ratio;
target.rotation = Quat::from_rotation_z(angle);
}
}
/// A lens to rotate a [`Transform`] component around a given fixed axis.
///
/// This lens interpolates the rotation angle of a [`Transform`] component from
/// a `start` value to an `end` value, for a rotation around a given axis. Unlike
/// [`TransformRotationLens`], it can produce an animation that rotates the entity
/// any number of turns around that axis.
///
/// See the [top-level `lens` module documentation] for a comparison of rotation lenses.
///
/// # Panics
///
/// This method panics if the `axis` vector is not normalized.
///
/// [`Transform`]: https://docs.rs/bevy/0.6.0/bevy/transform/components/struct.Transform.html
/// [top-level `lens` module documentation]: crate::lens
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TransformRotateAxisLens {
/// The normalized rotation axis.
pub axis: Vec3,
/// Start value of the rotation angle, in radians.
pub start: f32,
/// End value of the rotation angle, in radians.
pub end: f32,
}
impl Lens<Transform> for TransformRotateAxisLens {
fn lerp(&mut self, target: &mut Transform, ratio: f32) {
let angle = self.start + (self.end - self.start) * ratio;
target.rotation = Quat::from_axis_angle(self.axis, angle);
}
}
......@@ -196,6 +351,7 @@ impl Lens<Sprite> for SpriteColorLens {
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::TAU;
#[test]
fn text_color() {
......@@ -272,6 +428,136 @@ mod tests {
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
#[test]
fn transform_rotate_x() {
let mut lens = TransformRotateXLens {
start: 0.,
end: 1440_f32.to_radians(), // 4 turns
};
let mut transform = Transform::default();
for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
lens.lerp(&mut transform, *ratio);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
if index == 1 || index == 3 {
// For odd-numbered turns, the opposite Quat is produced. This is equivalent in
// terms of rotation to the IDENTITY one, but numerically the w component is not
// the same so would fail an equality test.
assert!(transform
.rotation
.abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
} else {
assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
}
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
lens.lerp(&mut transform, 0.1);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
assert!(transform
.rotation
.abs_diff_eq(Quat::from_rotation_x(0.1 * (4. * TAU)), 1e-5));
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
#[test]
fn transform_rotate_y() {
let mut lens = TransformRotateYLens {
start: 0.,
end: 1440_f32.to_radians(), // 4 turns
};
let mut transform = Transform::default();
for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
lens.lerp(&mut transform, *ratio);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
if index == 1 || index == 3 {
// For odd-numbered turns, the opposite Quat is produced. This is equivalent in
// terms of rotation to the IDENTITY one, but numerically the w component is not
// the same so would fail an equality test.
assert!(transform
.rotation
.abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
} else {
assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
}
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
lens.lerp(&mut transform, 0.1);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
assert!(transform
.rotation
.abs_diff_eq(Quat::from_rotation_y(0.1 * (4. * TAU)), 1e-5));
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
#[test]
fn transform_rotate_z() {
let mut lens = TransformRotateZLens {
start: 0.,
end: 1440_f32.to_radians(), // 4 turns
};
let mut transform = Transform::default();
for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
lens.lerp(&mut transform, *ratio);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
if index == 1 || index == 3 {
// For odd-numbered turns, the opposite Quat is produced. This is equivalent in
// terms of rotation to the IDENTITY one, but numerically the w component is not
// the same so would fail an equality test.
assert!(transform
.rotation
.abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
} else {
assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
}
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
lens.lerp(&mut transform, 0.1);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
assert!(transform
.rotation
.abs_diff_eq(Quat::from_rotation_z(0.1 * (4. * TAU)), 1e-5));
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
#[test]
fn transform_rotate_axis() {
let axis = Vec3::ONE.normalize();
let mut lens = TransformRotateAxisLens {
axis,
start: 0.,
end: 1440_f32.to_radians(), // 4 turns
};
let mut transform = Transform::default();
for (index, ratio) in [0., 0.25, 0.5, 0.75, 1.].iter().enumerate() {
lens.lerp(&mut transform, *ratio);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
if index == 1 || index == 3 {
// For odd-numbered turns, the opposite Quat is produced. This is equivalent in
// terms of rotation to the IDENTITY one, but numerically the w component is not
// the same so would fail an equality test.
assert!(transform
.rotation
.abs_diff_eq(Quat::from_xyzw(0., 0., 0., -1.), 1e-5));
} else {
assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
}
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
lens.lerp(&mut transform, 0.1);
assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
assert!(transform
.rotation
.abs_diff_eq(Quat::from_axis_angle(axis, 0.1 * (4. * TAU)), 1e-5));
assert!(transform.scale.abs_diff_eq(Vec3::ONE, 1e-5));
}
#[test]
fn transform_scale() {
let mut lens = TransformScaleLens {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment