diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index 50a5218d74577..05a600a4f876f 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -38,6 +38,30 @@ impl std::fmt::Display for InvalidDirectionError { } } +/// Checks that a vector with the given squared length is normalized. +/// +/// Warns for small error with a length threshold of approximately `1e-4`, +/// and panics for large error with a length threshold of approximately `1e-2`. +/// +/// The format used for the logged warning is `"Warning: {warning} The length is {length}`, +/// and similarly for the error. +#[cfg(debug_assertions)] +fn assert_is_normalized(message: &str, length_squared: f32) { + let length_error_squared = (length_squared - 1.0).abs(); + + // Panic for large error and warn for slight error. + if length_error_squared > 2e-2 || length_error_squared.is_nan() { + // Length error is approximately 1e-2 or more. + panic!("Error: {message} The length is {}.", length_squared.sqrt()); + } else if length_error_squared > 2e-4 { + // Length error is approximately 1e-4 or more. + eprintln!( + "Warning: {message} The length is {}.", + length_squared.sqrt() + ); + } +} + /// A normalized vector pointing in a direction in 2D space #[deprecated( since = "0.14.0", @@ -81,9 +105,13 @@ impl Dir2 { /// /// # Warning /// - /// `value` must be normalized, i.e it's length must be `1.0`. + /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec2) -> Self { - debug_assert!(value.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "The vector given to `Dir2::new_unchecked` is not normalized.", + value.length_squared(), + ); Self(value) } @@ -211,9 +239,13 @@ impl Dir3 { /// /// # Warning /// - /// `value` must be normalized, i.e it's length must be `1.0`. + /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec3) -> Self { - debug_assert!(value.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "The vector given to `Dir3::new_unchecked` is not normalized.", + value.length_squared(), + ); Self(value) } @@ -289,11 +321,13 @@ impl std::ops::Mul for Quat { fn mul(self, direction: Dir3) -> Self::Output { let rotated = self * *direction; - // Make sure the result is normalized. - // This can fail for non-unit quaternions. - debug_assert!(rotated.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "`Dir3` is denormalized after rotation.", + rotated.length_squared(), + ); - Dir3::new_unchecked(rotated) + Dir3(rotated) } } @@ -365,9 +399,13 @@ impl Dir3A { /// /// # Warning /// - /// `value` must be normalized, i.e it's length must be `1.0`. + /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec3A) -> Self { - debug_assert!(value.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "The vector given to `Dir3A::new_unchecked` is not normalized.", + value.length_squared(), + ); Self(value) } @@ -443,11 +481,13 @@ impl std::ops::Mul for Quat { fn mul(self, direction: Dir3A) -> Self::Output { let rotated = self * *direction; - // Make sure the result is normalized. - // This can fail for non-unit quaternions. - debug_assert!(rotated.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "`Dir3A` is denormalized after rotation.", + rotated.length_squared(), + ); - Dir3A::new_unchecked(rotated) + Dir3A(rotated) } }