use core::f32::consts::TAU;12use glam::FloatExt;34use crate::{5ops,6prelude::{Mat2, Vec2},7};89#[cfg(feature = "bevy_reflect")]10use bevy_reflect::{std_traits::ReflectDefault, Reflect};11#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]12use bevy_reflect::{ReflectDeserialize, ReflectSerialize};1314/// A 2D rotation.15///16/// # Example17///18/// ```19/// # use approx::assert_relative_eq;20/// # use bevy_math::{Rot2, Vec2};21/// use std::f32::consts::PI;22///23/// // Create rotations from counterclockwise angles in radians or degrees24/// let rotation1 = Rot2::radians(PI / 2.0);25/// let rotation2 = Rot2::degrees(45.0);26///27/// // Get the angle back as radians or degrees28/// assert_eq!(rotation1.as_degrees(), 90.0);29/// assert_eq!(rotation2.as_radians(), PI / 4.0);30///31/// // "Add" rotations together using `*`32/// #[cfg(feature = "approx")]33/// assert_relative_eq!(rotation1 * rotation2, Rot2::degrees(135.0));34///35/// // Rotate vectors36/// #[cfg(feature = "approx")]37/// assert_relative_eq!(rotation1 * Vec2::X, Vec2::Y);38/// ```39#[derive(Clone, Copy, Debug, PartialEq)]40#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]41#[cfg_attr(42feature = "bevy_reflect",43derive(Reflect),44reflect(Debug, PartialEq, Default, Clone)45)]46#[cfg_attr(47all(feature = "serialize", feature = "bevy_reflect"),48reflect(Serialize, Deserialize)49)]50#[doc(alias = "rotation", alias = "rotation2d", alias = "rotation_2d")]51pub struct Rot2 {52/// The cosine of the rotation angle.53///54/// This is the real part of the unit complex number representing the rotation.55pub cos: f32,56/// The sine of the rotation angle.57///58/// This is the imaginary part of the unit complex number representing the rotation.59pub sin: f32,60}6162impl Default for Rot2 {63fn default() -> Self {64Self::IDENTITY65}66}6768impl Rot2 {69/// No rotation.70/// Also equals a full turn that returns back to its original position.71/// ```72/// # use approx::assert_relative_eq;73/// # use bevy_math::Rot2;74/// #[cfg(feature = "approx")]75/// assert_relative_eq!(Rot2::IDENTITY, Rot2::degrees(360.0), epsilon = 2e-7);76/// ```77pub const IDENTITY: Self = Self { cos: 1.0, sin: 0.0 };7879/// A rotation of π radians.80/// Corresponds to a half-turn.81pub const PI: Self = Self {82cos: -1.0,83sin: 0.0,84};8586/// A counterclockwise rotation of π/2 radians.87/// Corresponds to a counterclockwise quarter-turn.88pub const FRAC_PI_2: Self = Self { cos: 0.0, sin: 1.0 };8990/// A counterclockwise rotation of π/3 radians.91/// Corresponds to a counterclockwise turn by 60°.92pub const FRAC_PI_3: Self = Self {93cos: 0.5,94sin: 0.866_025_4,95};9697/// A counterclockwise rotation of π/4 radians.98/// Corresponds to a counterclockwise turn by 45°.99pub const FRAC_PI_4: Self = Self {100cos: core::f32::consts::FRAC_1_SQRT_2,101sin: core::f32::consts::FRAC_1_SQRT_2,102};103104/// A counterclockwise rotation of π/6 radians.105/// Corresponds to a counterclockwise turn by 30°.106pub const FRAC_PI_6: Self = Self {107cos: 0.866_025_4,108sin: 0.5,109};110111/// A counterclockwise rotation of π/8 radians.112/// Corresponds to a counterclockwise turn by 22.5°.113pub const FRAC_PI_8: Self = Self {114cos: 0.923_879_5,115sin: 0.382_683_43,116};117118/// Creates a [`Rot2`] from a counterclockwise angle in radians.119/// A negative argument corresponds to a clockwise rotation.120///121/// # Note122///123/// Angles larger than or equal to 2π (in either direction) loop around to smaller rotations, since a full rotation returns an object to its starting orientation.124///125/// # Example126///127/// ```128/// # use bevy_math::Rot2;129/// # use approx::assert_relative_eq;130/// # use std::f32::consts::{FRAC_PI_2, PI};131///132/// let rot1 = Rot2::radians(3.0 * FRAC_PI_2);133/// let rot2 = Rot2::radians(-FRAC_PI_2);134/// #[cfg(feature = "approx")]135/// assert_relative_eq!(rot1, rot2);136///137/// let rot3 = Rot2::radians(PI);138/// #[cfg(feature = "approx")]139/// assert_relative_eq!(rot1 * rot1, rot3);140///141/// // A rotation by 3π and 1π are the same142/// #[cfg(feature = "approx")]143/// assert_relative_eq!(Rot2::radians(3.0 * PI), Rot2::radians(PI));144/// ```145#[inline]146pub fn radians(radians: f32) -> Self {147let (sin, cos) = ops::sin_cos(radians);148Self::from_sin_cos(sin, cos)149}150151/// Creates a [`Rot2`] from a counterclockwise angle in degrees.152/// A negative argument corresponds to a clockwise rotation.153///154/// # Note155///156/// Angles larger than or equal to 360° (in either direction) loop around to smaller rotations, since a full rotation returns an object to its starting orientation.157///158/// # Example159///160/// ```161/// # use bevy_math::Rot2;162/// # use approx::{assert_relative_eq, assert_abs_diff_eq};163///164/// let rot1 = Rot2::degrees(270.0);165/// let rot2 = Rot2::degrees(-90.0);166/// #[cfg(feature = "approx")]167/// assert_relative_eq!(rot1, rot2);168///169/// let rot3 = Rot2::degrees(180.0);170/// #[cfg(feature = "approx")]171/// assert_relative_eq!(rot1 * rot1, rot3);172///173/// // A rotation by 365° and 5° are the same174/// #[cfg(feature = "approx")]175/// assert_abs_diff_eq!(Rot2::degrees(365.0), Rot2::degrees(5.0), epsilon = 2e-7);176/// ```177#[inline]178pub fn degrees(degrees: f32) -> Self {179Self::radians(degrees.to_radians())180}181182/// Creates a [`Rot2`] from a counterclockwise fraction of a full turn of 360 degrees.183/// A negative argument corresponds to a clockwise rotation.184///185/// # Note186///187/// Angles larger than or equal to 1 turn (in either direction) loop around to smaller rotations, since a full rotation returns an object to its starting orientation.188///189/// # Example190///191/// ```192/// # use bevy_math::Rot2;193/// # use approx::assert_relative_eq;194///195/// let rot1 = Rot2::turn_fraction(0.75);196/// let rot2 = Rot2::turn_fraction(-0.25);197/// #[cfg(feature = "approx")]198/// assert_relative_eq!(rot1, rot2);199///200/// let rot3 = Rot2::turn_fraction(0.5);201/// #[cfg(feature = "approx")]202/// assert_relative_eq!(rot1 * rot1, rot3);203///204/// // A rotation by 1.5 turns and 0.5 turns are the same205/// #[cfg(feature = "approx")]206/// assert_relative_eq!(Rot2::turn_fraction(1.5), Rot2::turn_fraction(0.5));207/// ```208#[inline]209pub fn turn_fraction(fraction: f32) -> Self {210Self::radians(TAU * fraction)211}212213/// Creates a [`Rot2`] from the sine and cosine of an angle.214///215/// The rotation is only valid if `sin * sin + cos * cos == 1.0`.216///217/// # Panics218///219/// Panics if `sin * sin + cos * cos != 1.0` when the `glam_assert` feature is enabled.220#[inline]221pub fn from_sin_cos(sin: f32, cos: f32) -> Self {222let rotation = Self { sin, cos };223debug_assert!(224rotation.is_normalized(),225"the given sine and cosine produce an invalid rotation"226);227rotation228}229230/// Returns a corresponding rotation angle in radians in the `(-pi, pi]` range.231#[inline]232pub fn as_radians(self) -> f32 {233ops::atan2(self.sin, self.cos)234}235236/// Returns a corresponding rotation angle in degrees in the `(-180, 180]` range.237#[inline]238pub fn as_degrees(self) -> f32 {239self.as_radians().to_degrees()240}241242/// Returns a corresponding rotation angle as a fraction of a full 360 degree turn in the `(-0.5, 0.5]` range.243#[inline]244pub fn as_turn_fraction(self) -> f32 {245self.as_radians() / TAU246}247248/// Returns the sine and cosine of the rotation angle.249#[inline]250pub const fn sin_cos(self) -> (f32, f32) {251(self.sin, self.cos)252}253254/// Computes the length or norm of the complex number used to represent the rotation.255///256/// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations257/// can be a result of incorrect construction or floating point error caused by258/// successive operations.259#[inline]260#[doc(alias = "norm")]261pub fn length(self) -> f32 {262Vec2::new(self.sin, self.cos).length()263}264265/// Computes the squared length or norm of the complex number used to represent the rotation.266///267/// This is generally faster than [`Rot2::length()`], as it avoids a square268/// root operation.269///270/// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations271/// can be a result of incorrect construction or floating point error caused by272/// successive operations.273#[inline]274#[doc(alias = "norm2")]275pub fn length_squared(self) -> f32 {276Vec2::new(self.sin, self.cos).length_squared()277}278279/// Computes `1.0 / self.length()`.280///281/// For valid results, `self` must _not_ have a length of zero.282#[inline]283pub fn length_recip(self) -> f32 {284Vec2::new(self.sin, self.cos).length_recip()285}286287/// Returns `self` with a length of `1.0` if possible, and `None` otherwise.288///289/// `None` will be returned if the sine and cosine of `self` are both zero (or very close to zero),290/// or if either of them is NaN or infinite.291///292/// Note that [`Rot2`] should typically already be normalized by design.293/// Manual normalization is only needed when successive operations result in294/// accumulated floating point error, or if the rotation was constructed295/// with invalid values.296#[inline]297pub fn try_normalize(self) -> Option<Self> {298let recip = self.length_recip();299if recip.is_finite() && recip > 0.0 {300Some(Self::from_sin_cos(self.sin * recip, self.cos * recip))301} else {302None303}304}305306/// Returns `self` with a length of `1.0`.307///308/// Note that [`Rot2`] should typically already be normalized by design.309/// Manual normalization is only needed when successive operations result in310/// accumulated floating point error, or if the rotation was constructed311/// with invalid values.312///313/// # Panics314///315/// Panics if `self` has a length of zero, NaN, or infinity when debug assertions are enabled.316#[inline]317pub fn normalize(self) -> Self {318let length_recip = self.length_recip();319Self::from_sin_cos(self.sin * length_recip, self.cos * length_recip)320}321322/// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.323/// Useful for preventing numerical error accumulation.324/// See [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for an example of when such error accumulation might occur.325#[inline]326pub fn fast_renormalize(self) -> Self {327let length_squared = self.length_squared();328// Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for more details.329let length_recip_approx = 0.5 * (3.0 - length_squared);330Rot2 {331sin: self.sin * length_recip_approx,332cos: self.cos * length_recip_approx,333}334}335336/// Returns `true` if the rotation is neither infinite nor NaN.337#[inline]338pub const fn is_finite(self) -> bool {339self.sin.is_finite() && self.cos.is_finite()340}341342/// Returns `true` if the rotation is NaN.343#[inline]344pub const fn is_nan(self) -> bool {345self.sin.is_nan() || self.cos.is_nan()346}347348/// Returns whether `self` has a length of `1.0` or not.349///350/// Uses a precision threshold of approximately `1e-4`.351#[inline]352pub fn is_normalized(self) -> bool {353// The allowed length is 1 +/- 1e-4, so the largest allowed354// squared length is (1 + 1e-4)^2 = 1.00020001, which makes355// the threshold for the squared length approximately 2e-4.356ops::abs(self.length_squared() - 1.0) <= 2e-4357}358359/// Returns `true` if the rotation is near [`Rot2::IDENTITY`].360#[inline]361pub fn is_near_identity(self) -> bool {362// Same as `Quat::is_near_identity`, but using sine and cosine363let threshold_angle_sin = 0.000_049_692_047; // let threshold_angle = 0.002_847_144_6;364self.cos > 0.0 && ops::abs(self.sin) < threshold_angle_sin365}366367/// Returns the angle in radians needed to make `self` and `other` coincide.368#[inline]369pub fn angle_to(self, other: Self) -> f32 {370(other * self.inverse()).as_radians()371}372373/// Returns the inverse of the rotation. This is also the conjugate374/// of the unit complex number representing the rotation.375#[inline]376#[must_use]377#[doc(alias = "conjugate")]378pub const fn inverse(self) -> Self {379Self {380cos: self.cos,381sin: -self.sin,382}383}384385/// Performs a linear interpolation between `self` and `rhs` based on386/// the value `s`, and normalizes the rotation afterwards.387///388/// When `s == 0.0`, the result will be equal to `self`.389/// When `s == 1.0`, the result will be equal to `rhs`.390///391/// This is slightly more efficient than [`slerp`](Self::slerp), and produces a similar result392/// when the difference between the two rotations is small. At larger differences,393/// the result resembles a kind of ease-in-out effect.394///395/// If you would like the angular velocity to remain constant, consider using [`slerp`](Self::slerp) instead.396///397/// # Details398///399/// `nlerp` corresponds to computing an angle for a point at position `s` on a line drawn400/// between the endpoints of the arc formed by `self` and `rhs` on a unit circle,401/// and normalizing the result afterwards.402///403/// Note that if the angles are opposite like 0 and π, the line will pass through the origin,404/// and the resulting angle will always be either `self` or `rhs` depending on `s`.405/// If `s` happens to be `0.5` in this case, a valid rotation cannot be computed, and `self`406/// will be returned as a fallback.407///408/// # Example409///410/// ```411/// # use bevy_math::Rot2;412/// #413/// let rot1 = Rot2::IDENTITY;414/// let rot2 = Rot2::degrees(135.0);415///416/// let result1 = rot1.nlerp(rot2, 1.0 / 3.0);417/// assert_eq!(result1.as_degrees(), 28.675055);418///419/// let result2 = rot1.nlerp(rot2, 0.5);420/// assert_eq!(result2.as_degrees(), 67.5);421/// ```422#[inline]423pub fn nlerp(self, end: Self, s: f32) -> Self {424Self {425sin: self.sin.lerp(end.sin, s),426cos: self.cos.lerp(end.cos, s),427}428.try_normalize()429// Fall back to the start rotation.430// This can happen when `self` and `end` are opposite angles and `s == 0.5`,431// because the resulting rotation would be zero, which cannot be normalized.432.unwrap_or(self)433}434435/// Performs a spherical linear interpolation between `self` and `end`436/// based on the value `s`.437///438/// This corresponds to interpolating between the two angles at a constant angular velocity.439///440/// When `s == 0.0`, the result will be equal to `self`.441/// When `s == 1.0`, the result will be equal to `rhs`.442///443/// If you would like the rotation to have a kind of ease-in-out effect, consider444/// using the slightly more efficient [`nlerp`](Self::nlerp) instead.445///446/// # Example447///448/// ```449/// # use bevy_math::Rot2;450/// #451/// let rot1 = Rot2::IDENTITY;452/// let rot2 = Rot2::degrees(135.0);453///454/// let result1 = rot1.slerp(rot2, 1.0 / 3.0);455/// assert_eq!(result1.as_degrees(), 45.0);456///457/// let result2 = rot1.slerp(rot2, 0.5);458/// assert_eq!(result2.as_degrees(), 67.5);459/// ```460#[inline]461pub fn slerp(self, end: Self, s: f32) -> Self {462self * Self::radians(self.angle_to(end) * s)463}464}465466impl From<f32> for Rot2 {467/// Creates a [`Rot2`] from a counterclockwise angle in radians.468fn from(rotation: f32) -> Self {469Self::radians(rotation)470}471}472473impl From<Rot2> for Mat2 {474/// Creates a [`Mat2`] rotation matrix from a [`Rot2`].475fn from(rot: Rot2) -> Self {476Mat2::from_cols_array(&[rot.cos, rot.sin, -rot.sin, rot.cos])477}478}479480impl core::ops::Mul for Rot2 {481type Output = Self;482483fn mul(self, rhs: Self) -> Self::Output {484Self {485cos: self.cos * rhs.cos - self.sin * rhs.sin,486sin: self.sin * rhs.cos + self.cos * rhs.sin,487}488}489}490491impl core::ops::MulAssign for Rot2 {492fn mul_assign(&mut self, rhs: Self) {493*self = *self * rhs;494}495}496497impl core::ops::Mul<Vec2> for Rot2 {498type Output = Vec2;499500/// Rotates a [`Vec2`] by a [`Rot2`].501fn mul(self, rhs: Vec2) -> Self::Output {502Vec2::new(503rhs.x * self.cos - rhs.y * self.sin,504rhs.x * self.sin + rhs.y * self.cos,505)506}507}508509#[cfg(any(feature = "approx", test))]510impl approx::AbsDiffEq for Rot2 {511type Epsilon = f32;512fn default_epsilon() -> f32 {513f32::EPSILON514}515fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {516self.cos.abs_diff_eq(&other.cos, epsilon) && self.sin.abs_diff_eq(&other.sin, epsilon)517}518}519520#[cfg(any(feature = "approx", test))]521impl approx::RelativeEq for Rot2 {522fn default_max_relative() -> f32 {523f32::EPSILON524}525fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {526self.cos.relative_eq(&other.cos, epsilon, max_relative)527&& self.sin.relative_eq(&other.sin, epsilon, max_relative)528}529}530531#[cfg(any(feature = "approx", test))]532impl approx::UlpsEq for Rot2 {533fn default_max_ulps() -> u32 {5344535}536fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {537self.cos.ulps_eq(&other.cos, epsilon, max_ulps)538&& self.sin.ulps_eq(&other.sin, epsilon, max_ulps)539}540}541542#[cfg(test)]543mod tests {544use core::f32::consts::FRAC_PI_2;545546use approx::assert_relative_eq;547548use crate::{ops, Dir2, Mat2, Rot2, Vec2};549550#[test]551fn creation() {552let rotation1 = Rot2::radians(FRAC_PI_2);553let rotation2 = Rot2::degrees(90.0);554let rotation3 = Rot2::from_sin_cos(1.0, 0.0);555let rotation4 = Rot2::turn_fraction(0.25);556557// All three rotations should be equal558assert_relative_eq!(rotation1.sin, rotation2.sin);559assert_relative_eq!(rotation1.cos, rotation2.cos);560assert_relative_eq!(rotation1.sin, rotation3.sin);561assert_relative_eq!(rotation1.cos, rotation3.cos);562assert_relative_eq!(rotation1.sin, rotation4.sin);563assert_relative_eq!(rotation1.cos, rotation4.cos);564565// The rotation should be 90 degrees566assert_relative_eq!(rotation1.as_radians(), FRAC_PI_2);567assert_relative_eq!(rotation1.as_degrees(), 90.0);568assert_relative_eq!(rotation1.as_turn_fraction(), 0.25);569}570571#[test]572fn rotate() {573let rotation = Rot2::degrees(90.0);574575assert_relative_eq!(rotation * Vec2::X, Vec2::Y);576assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X);577}578579#[test]580fn rotation_range() {581// the rotation range is `(-180, 180]` and the constructors582// normalize the rotations to that range583assert_relative_eq!(Rot2::radians(3.0 * FRAC_PI_2), Rot2::radians(-FRAC_PI_2));584assert_relative_eq!(Rot2::degrees(270.0), Rot2::degrees(-90.0));585assert_relative_eq!(Rot2::turn_fraction(0.75), Rot2::turn_fraction(-0.25));586}587588#[test]589fn add() {590let rotation1 = Rot2::degrees(90.0);591let rotation2 = Rot2::degrees(180.0);592593// 90 deg + 180 deg becomes -90 deg after it wraps around to be within the `(-180, 180]` range594assert_eq!((rotation1 * rotation2).as_degrees(), -90.0);595}596597#[test]598fn subtract() {599let rotation1 = Rot2::degrees(90.0);600let rotation2 = Rot2::degrees(45.0);601602assert_relative_eq!((rotation1 * rotation2.inverse()).as_degrees(), 45.0);603604// This should be equivalent to the above605assert_relative_eq!(rotation2.angle_to(rotation1), core::f32::consts::FRAC_PI_4);606}607608#[test]609fn length() {610let rotation = Rot2 {611sin: 10.0,612cos: 5.0,613};614615assert_eq!(rotation.length_squared(), 125.0);616assert_eq!(rotation.length(), 11.18034);617assert!(ops::abs(rotation.normalize().length() - 1.0) < 10e-7);618}619620#[test]621fn is_near_identity() {622assert!(!Rot2::radians(0.1).is_near_identity());623assert!(!Rot2::radians(-0.1).is_near_identity());624assert!(Rot2::radians(0.00001).is_near_identity());625assert!(Rot2::radians(-0.00001).is_near_identity());626assert!(Rot2::radians(0.0).is_near_identity());627}628629#[test]630fn normalize() {631let rotation = Rot2 {632sin: 10.0,633cos: 5.0,634};635let normalized_rotation = rotation.normalize();636637assert_eq!(normalized_rotation.sin, 0.89442724);638assert_eq!(normalized_rotation.cos, 0.44721362);639640assert!(!rotation.is_normalized());641assert!(normalized_rotation.is_normalized());642}643644#[test]645fn fast_renormalize() {646let rotation = Rot2 { sin: 1.0, cos: 0.5 };647let normalized_rotation = rotation.normalize();648649let mut unnormalized_rot = rotation;650let mut renormalized_rot = rotation;651let mut initially_normalized_rot = normalized_rotation;652let mut fully_normalized_rot = normalized_rotation;653654// Compute a 64x (=2⁶) multiple of the rotation.655for _ in 0..6 {656unnormalized_rot = unnormalized_rot * unnormalized_rot;657renormalized_rot = renormalized_rot * renormalized_rot;658initially_normalized_rot = initially_normalized_rot * initially_normalized_rot;659fully_normalized_rot = fully_normalized_rot * fully_normalized_rot;660661renormalized_rot = renormalized_rot.fast_renormalize();662fully_normalized_rot = fully_normalized_rot.normalize();663}664665assert!(!unnormalized_rot.is_normalized());666667assert!(renormalized_rot.is_normalized());668assert!(fully_normalized_rot.is_normalized());669670assert_relative_eq!(fully_normalized_rot, renormalized_rot, epsilon = 0.000001);671assert_relative_eq!(672fully_normalized_rot,673unnormalized_rot.normalize(),674epsilon = 0.000001675);676assert_relative_eq!(677fully_normalized_rot,678initially_normalized_rot.normalize(),679epsilon = 0.000001680);681}682683#[test]684fn try_normalize() {685// Valid686assert!(Rot2 {687sin: 10.0,688cos: 5.0,689}690.try_normalize()691.is_some());692693// NaN694assert!(Rot2 {695sin: f32::NAN,696cos: 5.0,697}698.try_normalize()699.is_none());700701// Zero702assert!(Rot2 { sin: 0.0, cos: 0.0 }.try_normalize().is_none());703704// Non-finite705assert!(Rot2 {706sin: f32::INFINITY,707cos: 5.0,708}709.try_normalize()710.is_none());711}712713#[test]714fn nlerp() {715let rot1 = Rot2::IDENTITY;716let rot2 = Rot2::degrees(135.0);717718assert_eq!(rot1.nlerp(rot2, 1.0 / 3.0).as_degrees(), 28.675055);719assert!(rot1.nlerp(rot2, 0.0).is_near_identity());720assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 67.5);721assert_eq!(rot1.nlerp(rot2, 1.0).as_degrees(), 135.0);722723let rot1 = Rot2::IDENTITY;724let rot2 = Rot2::from_sin_cos(0.0, -1.0);725726assert!(rot1.nlerp(rot2, 1.0 / 3.0).is_near_identity());727assert!(rot1.nlerp(rot2, 0.0).is_near_identity());728// At 0.5, there is no valid rotation, so the fallback is the original angle.729assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 0.0);730assert_eq!(ops::abs(rot1.nlerp(rot2, 1.0).as_degrees()), 180.0);731}732733#[test]734fn slerp() {735let rot1 = Rot2::IDENTITY;736let rot2 = Rot2::degrees(135.0);737738assert_eq!(rot1.slerp(rot2, 1.0 / 3.0).as_degrees(), 45.0);739assert!(rot1.slerp(rot2, 0.0).is_near_identity());740assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 67.5);741assert_eq!(rot1.slerp(rot2, 1.0).as_degrees(), 135.0);742743let rot1 = Rot2::IDENTITY;744let rot2 = Rot2::from_sin_cos(0.0, -1.0);745746assert!(ops::abs(rot1.slerp(rot2, 1.0 / 3.0).as_degrees() - 60.0) < 10e-6);747assert!(rot1.slerp(rot2, 0.0).is_near_identity());748assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 90.0);749assert_eq!(ops::abs(rot1.slerp(rot2, 1.0).as_degrees()), 180.0);750}751752#[test]753fn rotation_matrix() {754let rotation = Rot2::degrees(90.0);755let matrix: Mat2 = rotation.into();756757// Check that the matrix is correct.758assert_relative_eq!(matrix.x_axis, Vec2::Y);759assert_relative_eq!(matrix.y_axis, Vec2::NEG_X);760761// Check that the matrix rotates vectors correctly.762assert_relative_eq!(matrix * Vec2::X, Vec2::Y);763assert_relative_eq!(matrix * Vec2::Y, Vec2::NEG_X);764assert_relative_eq!(matrix * Vec2::NEG_X, Vec2::NEG_Y);765assert_relative_eq!(matrix * Vec2::NEG_Y, Vec2::X);766}767}768769770