Rotation api extension (#15169)
# Objective - Another way of specifying rotations was requested in https://github.com/bevyengine/bevy/issues/11132#issuecomment-2344603178 ## Solution - Add methods on `Rot2` - `turn_fraction(fraction: f32) -> Self` - `as_turn_fraction(self) -> f32` - Also add some documentation on range of rotation ## Testing - extended existing tests - added new tests ## Showcase ```rust let rotation1 = Rot2::degrees(90.0); let rotation2 = Rot2::turn_fraction(0.25); // rotations should be equal assert_relative_eq!(rotation1, rotation2); // The rotation should be 90 degrees assert_relative_eq!(rotation2.as_radians(), FRAC_PI_2); assert_relative_eq!(rotation2.as_degrees(), 90.0); ``` --------- Co-authored-by: Joona Aalto <jondolf.dev@gmail.com> Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
This commit is contained in:
		
							parent
							
								
									17b1bcde95
								
							
						
					
					
						commit
						29c4c79342
					
				| @ -1,3 +1,5 @@ | |||||||
|  | use std::f32::consts::TAU; | ||||||
|  | 
 | ||||||
| use glam::FloatExt; | use glam::FloatExt; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -100,6 +102,26 @@ impl Rot2 { | |||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     /// Creates a [`Rot2`] from a counterclockwise angle in radians.
 |     /// Creates a [`Rot2`] from a counterclockwise angle in radians.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Note
 | ||||||
|  |     ///
 | ||||||
|  |     /// The input rotation will always be clamped to the range `(-π, π]` by design.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Example
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// # use bevy_math::Rot2;
 | ||||||
|  |     /// # use approx::assert_relative_eq;
 | ||||||
|  |     /// # use std::f32::consts::{FRAC_PI_2, PI};
 | ||||||
|  |     ///
 | ||||||
|  |     /// let rot1 = Rot2::radians(3.0 * FRAC_PI_2);
 | ||||||
|  |     /// let rot2 = Rot2::radians(-FRAC_PI_2);
 | ||||||
|  |     /// assert_relative_eq!(rot1, rot2);
 | ||||||
|  |     ///
 | ||||||
|  |     /// let rot3 = Rot2::radians(PI);
 | ||||||
|  |     /// assert_relative_eq!(rot1 * rot1, rot3);
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn radians(radians: f32) -> Self { |     pub fn radians(radians: f32) -> Self { | ||||||
|         let (sin, cos) = ops::sin_cos(radians); |         let (sin, cos) = ops::sin_cos(radians); | ||||||
| @ -107,11 +129,55 @@ impl Rot2 { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Creates a [`Rot2`] from a counterclockwise angle in degrees.
 |     /// Creates a [`Rot2`] from a counterclockwise angle in degrees.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Note
 | ||||||
|  |     ///
 | ||||||
|  |     /// The input rotation will always be clamped to the range `(-180°, 180°]` by design.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Example
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// # use bevy_math::Rot2;
 | ||||||
|  |     /// # use approx::assert_relative_eq;
 | ||||||
|  |     ///
 | ||||||
|  |     /// let rot1 = Rot2::degrees(270.0);
 | ||||||
|  |     /// let rot2 = Rot2::degrees(-90.0);
 | ||||||
|  |     /// assert_relative_eq!(rot1, rot2);
 | ||||||
|  |     ///
 | ||||||
|  |     /// let rot3 = Rot2::degrees(180.0);
 | ||||||
|  |     /// assert_relative_eq!(rot1 * rot1, rot3);
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn degrees(degrees: f32) -> Self { |     pub fn degrees(degrees: f32) -> Self { | ||||||
|         Self::radians(degrees.to_radians()) |         Self::radians(degrees.to_radians()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// Creates a [`Rot2`] from a counterclockwise fraction of a full turn of 360 degrees.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Note
 | ||||||
|  |     ///
 | ||||||
|  |     /// The input rotation will always be clamped to the range `(-50%, 50%]` by design.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Example
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// # use bevy_math::Rot2;
 | ||||||
|  |     /// # use approx::assert_relative_eq;
 | ||||||
|  |     ///
 | ||||||
|  |     /// let rot1 = Rot2::turn_fraction(0.75);
 | ||||||
|  |     /// let rot2 = Rot2::turn_fraction(-0.25);
 | ||||||
|  |     /// assert_relative_eq!(rot1, rot2);
 | ||||||
|  |     ///
 | ||||||
|  |     /// let rot3 = Rot2::turn_fraction(0.5);
 | ||||||
|  |     /// assert_relative_eq!(rot1 * rot1, rot3);
 | ||||||
|  |     ///
 | ||||||
|  |     /// ```
 | ||||||
|  |     #[inline] | ||||||
|  |     pub fn turn_fraction(fraction: f32) -> Self { | ||||||
|  |         Self::radians(TAU * fraction) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Creates a [`Rot2`] from the sine and cosine of an angle in radians.
 |     /// Creates a [`Rot2`] from the sine and cosine of an angle in radians.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The rotation is only valid if `sin * sin + cos * cos == 1.0`.
 |     /// The rotation is only valid if `sin * sin + cos * cos == 1.0`.
 | ||||||
| @ -141,6 +207,12 @@ impl Rot2 { | |||||||
|         self.as_radians().to_degrees() |         self.as_radians().to_degrees() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// Returns the rotation as a fraction of a full 360 degree turn.
 | ||||||
|  |     #[inline] | ||||||
|  |     pub fn as_turn_fraction(self) -> f32 { | ||||||
|  |         self.as_radians() / TAU | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Returns the sine and cosine of the rotation angle in radians.
 |     /// Returns the sine and cosine of the rotation angle in radians.
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub const fn sin_cos(self) -> (f32, f32) { |     pub const fn sin_cos(self) -> (f32, f32) { | ||||||
| @ -437,25 +509,31 @@ impl approx::UlpsEq for Rot2 { | |||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|  |     use std::f32::consts::FRAC_PI_2; | ||||||
|  | 
 | ||||||
|     use approx::assert_relative_eq; |     use approx::assert_relative_eq; | ||||||
| 
 | 
 | ||||||
|     use crate::{Dir2, Rot2, Vec2}; |     use crate::{Dir2, Rot2, Vec2}; | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn creation() { |     fn creation() { | ||||||
|         let rotation1 = Rot2::radians(std::f32::consts::FRAC_PI_2); |         let rotation1 = Rot2::radians(FRAC_PI_2); | ||||||
|         let rotation2 = Rot2::degrees(90.0); |         let rotation2 = Rot2::degrees(90.0); | ||||||
|         let rotation3 = Rot2::from_sin_cos(1.0, 0.0); |         let rotation3 = Rot2::from_sin_cos(1.0, 0.0); | ||||||
|  |         let rotation4 = Rot2::turn_fraction(0.25); | ||||||
| 
 | 
 | ||||||
|         // All three rotations should be equal
 |         // All three rotations should be equal
 | ||||||
|         assert_relative_eq!(rotation1.sin, rotation2.sin); |         assert_relative_eq!(rotation1.sin, rotation2.sin); | ||||||
|         assert_relative_eq!(rotation1.cos, rotation2.cos); |         assert_relative_eq!(rotation1.cos, rotation2.cos); | ||||||
|         assert_relative_eq!(rotation1.sin, rotation3.sin); |         assert_relative_eq!(rotation1.sin, rotation3.sin); | ||||||
|         assert_relative_eq!(rotation1.cos, rotation3.cos); |         assert_relative_eq!(rotation1.cos, rotation3.cos); | ||||||
|  |         assert_relative_eq!(rotation1.sin, rotation4.sin); | ||||||
|  |         assert_relative_eq!(rotation1.cos, rotation4.cos); | ||||||
| 
 | 
 | ||||||
|         // The rotation should be 90 degrees
 |         // The rotation should be 90 degrees
 | ||||||
|         assert_relative_eq!(rotation1.as_radians(), std::f32::consts::FRAC_PI_2); |         assert_relative_eq!(rotation1.as_radians(), FRAC_PI_2); | ||||||
|         assert_relative_eq!(rotation1.as_degrees(), 90.0); |         assert_relative_eq!(rotation1.as_degrees(), 90.0); | ||||||
|  |         assert_relative_eq!(rotation1.as_turn_fraction(), 0.25); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
| @ -466,12 +544,21 @@ mod tests { | |||||||
|         assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X); |         assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn rotation_range() { | ||||||
|  |         // the rotation range is `(-180, 180]` and the constructors
 | ||||||
|  |         // normalize the rotations to that range
 | ||||||
|  |         assert_relative_eq!(Rot2::radians(3.0 * FRAC_PI_2), Rot2::radians(-FRAC_PI_2)); | ||||||
|  |         assert_relative_eq!(Rot2::degrees(270.0), Rot2::degrees(-90.0)); | ||||||
|  |         assert_relative_eq!(Rot2::turn_fraction(0.75), Rot2::turn_fraction(-0.25)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn add() { |     fn add() { | ||||||
|         let rotation1 = Rot2::degrees(90.0); |         let rotation1 = Rot2::degrees(90.0); | ||||||
|         let rotation2 = Rot2::degrees(180.0); |         let rotation2 = Rot2::degrees(180.0); | ||||||
| 
 | 
 | ||||||
|         // 90 deg + 180 deg becomes -90 deg after it wraps around to be within the ]-180, 180] range
 |         // 90 deg + 180 deg becomes -90 deg after it wraps around to be within the `(-180, 180]` range
 | ||||||
|         assert_eq!((rotation1 * rotation2).as_degrees(), -90.0); |         assert_eq!((rotation1 * rotation2).as_degrees(), -90.0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Robert Walter
						Robert Walter