Fixed opposite quaternion slerp bug (#515)
* Fixed opposite quaternion slerp bug * Removed an unnecessary * nlerp and slerp always take the shortest path now
This commit is contained in:
parent
c96cd57efc
commit
816c043223
9 changed files with 264 additions and 31 deletions
2
.vscode/settings.json
vendored
Normal file
2
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ extern crate cgmath;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
use cgmath::*;
|
use cgmath::*;
|
||||||
|
|
|
@ -20,7 +20,7 @@ extern crate cgmath;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ extern crate cgmath;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ extern crate cgmath;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
//! distinguishes them from vectors, which have a length and direction, but do
|
//! distinguishes them from vectors, which have a length and direction, but do
|
||||||
//! not have a fixed position.
|
//! not have a fixed position.
|
||||||
|
|
||||||
use num_traits::{Float, Bounded, NumCast};
|
use num_traits::{Bounded, Float, NumCast};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
|
|
|
@ -106,7 +106,14 @@ impl<S: BaseFloat> Quaternion<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do a normalized linear interpolation with `other`, by `amount`.
|
/// Do a normalized linear interpolation with `other`, by `amount`.
|
||||||
pub fn nlerp(self, other: Quaternion<S>, amount: S) -> Quaternion<S> {
|
///
|
||||||
|
/// This takes the shortest path, so if the quaternions have a negative
|
||||||
|
/// dot product, the interpolation will be between `self` and `-other`.
|
||||||
|
pub fn nlerp(self, mut other: Quaternion<S>, amount: S) -> Quaternion<S> {
|
||||||
|
if self.dot(other) < S::zero() {
|
||||||
|
other = -other;
|
||||||
|
}
|
||||||
|
|
||||||
(self * (S::one() - amount) + other * amount).normalize()
|
(self * (S::one() - amount) + other * amount).normalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +122,9 @@ impl<S: BaseFloat> Quaternion<S> {
|
||||||
/// Return the spherical linear interpolation between the quaternion and
|
/// Return the spherical linear interpolation between the quaternion and
|
||||||
/// `other`. Both quaternions should be normalized first.
|
/// `other`. Both quaternions should be normalized first.
|
||||||
///
|
///
|
||||||
|
/// This takes the shortest path, so if the quaternions have a negative
|
||||||
|
/// dot product, the interpolation will be between `self` and `-other`.
|
||||||
|
///
|
||||||
/// # Performance notes
|
/// # Performance notes
|
||||||
///
|
///
|
||||||
/// The `acos` operation used in `slerp` is an expensive operation, so
|
/// The `acos` operation used in `slerp` is an expensive operation, so
|
||||||
|
@ -126,30 +136,28 @@ impl<S: BaseFloat> Quaternion<S> {
|
||||||
/// (http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/)
|
/// (http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/)
|
||||||
/// - [Arcsynthesis OpenGL tutorial]
|
/// - [Arcsynthesis OpenGL tutorial]
|
||||||
/// (http://www.arcsynthesis.org/gltut/Positioning/Tut08%20Interpolation.html)
|
/// (http://www.arcsynthesis.org/gltut/Positioning/Tut08%20Interpolation.html)
|
||||||
pub fn slerp(self, other: Quaternion<S>, amount: S) -> Quaternion<S> {
|
pub fn slerp(self, mut other: Quaternion<S>, amount: S) -> Quaternion<S> {
|
||||||
let dot = self.dot(other);
|
let mut dot = self.dot(other);
|
||||||
let dot_threshold = cast(0.9995f64).unwrap();
|
let dot_threshold: S = cast(0.9995f64).unwrap();
|
||||||
|
|
||||||
|
if dot < S::zero() {
|
||||||
|
other = -other;
|
||||||
|
dot = -dot;
|
||||||
|
}
|
||||||
|
|
||||||
// if quaternions are close together use `nlerp`
|
// if quaternions are close together use `nlerp`
|
||||||
if dot > dot_threshold {
|
if dot > dot_threshold {
|
||||||
self.nlerp(other, amount)
|
self.nlerp(other, amount)
|
||||||
} else {
|
} else {
|
||||||
// stay within the domain of acos()
|
// stay within the domain of acos()
|
||||||
// TODO REMOVE WHEN https://github.com/mozilla/rust/issues/12068 IS RESOLVED
|
let robust_dot = dot.min(S::one()).max(-S::one());
|
||||||
let robust_dot = if dot > S::one() {
|
|
||||||
S::one()
|
|
||||||
} else if dot < -S::one() {
|
|
||||||
-S::one()
|
|
||||||
} else {
|
|
||||||
dot
|
|
||||||
};
|
|
||||||
|
|
||||||
let theta = Rad::acos(robust_dot.clone());
|
let theta = Rad::acos(robust_dot);
|
||||||
|
|
||||||
let scale1 = Rad::sin(theta * (S::one() - amount));
|
let scale1 = Rad::sin(theta * (S::one() - amount));
|
||||||
let scale2 = Rad::sin(theta * amount);
|
let scale2 = Rad::sin(theta * amount);
|
||||||
|
|
||||||
(self * scale1 + other * scale2) * Rad::sin(theta).recip()
|
(self * scale1 + other * scale2).normalize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -758,4 +766,197 @@ mod tests {
|
||||||
assert_eq!(v, &QUATERNION);
|
assert_eq!(v, &QUATERNION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_same() {
|
||||||
|
let q = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
assert_ulps_eq!(q, q.nlerp(q, 0.1234));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_start() {
|
||||||
|
let q = Quaternion::from([0.5f64.sqrt(), 0.0, 0.5f64.sqrt(), 0.0]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
assert_ulps_eq!(q, q.nlerp(r, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_end() {
|
||||||
|
let q = Quaternion::from([0.5f64.sqrt(), 0.0, 0.5f64.sqrt(), 0.0]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
assert_ulps_eq!(r, q.nlerp(r, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_half() {
|
||||||
|
let q = Quaternion::from([-0.5, 0.5, 0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected =
|
||||||
|
Quaternion::from([0.0, 1.0 / 3f64.sqrt(), 1.0 / 3f64.sqrt(), 1.0 / 3f64.sqrt()]);
|
||||||
|
assert_ulps_eq!(expected, q.nlerp(r, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_quarter() {
|
||||||
|
let q = Quaternion::from([-0.5, 0.5, 0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([
|
||||||
|
-1.0 / 13f64.sqrt(),
|
||||||
|
2.0 / 13f64.sqrt(),
|
||||||
|
2.0 / 13f64.sqrt(),
|
||||||
|
2.0 / 13f64.sqrt(),
|
||||||
|
]);
|
||||||
|
assert_ulps_eq!(expected, q.nlerp(r, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_zero_dot() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, 0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([
|
||||||
|
-1.0 / 10f64.sqrt(),
|
||||||
|
-1.0 / 10f64.sqrt(),
|
||||||
|
2.0 / 10f64.sqrt(),
|
||||||
|
2.0 / 10f64.sqrt(),
|
||||||
|
]);
|
||||||
|
assert_ulps_eq!(expected, q.nlerp(r, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_negative_dot() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, -0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([
|
||||||
|
-2.0 / 13f64.sqrt(),
|
||||||
|
-2.0 / 13f64.sqrt(),
|
||||||
|
-2.0 / 13f64.sqrt(),
|
||||||
|
1.0 / 13f64.sqrt(),
|
||||||
|
]);
|
||||||
|
assert_ulps_eq!(expected, q.nlerp(r, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_opposite() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, -0.5, -0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
assert_ulps_eq!(q, q.nlerp(r, 0.25));
|
||||||
|
assert_ulps_eq!(q, q.nlerp(r, 0.75));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nlerp_extrapolate() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, -0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([
|
||||||
|
-1.0 / 12f64.sqrt(),
|
||||||
|
-1.0 / 12f64.sqrt(),
|
||||||
|
-1.0 / 12f64.sqrt(),
|
||||||
|
3.0 / 12f64.sqrt(),
|
||||||
|
]);
|
||||||
|
assert_ulps_eq!(expected, q.nlerp(r, -1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_same() {
|
||||||
|
let q = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
assert_ulps_eq!(q, q.slerp(q, 0.1234));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_start() {
|
||||||
|
let q = Quaternion::from([0.5f64.sqrt(), 0.0, 0.5f64.sqrt(), 0.0]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
assert_ulps_eq!(q, q.slerp(r, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_end() {
|
||||||
|
let q = Quaternion::from([0.5f64.sqrt(), 0.0, 0.5f64.sqrt(), 0.0]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
assert_ulps_eq!(r, q.slerp(r, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_half() {
|
||||||
|
let q = Quaternion::from([-0.5, 0.5, 0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected =
|
||||||
|
Quaternion::from([0.0, 1.0 / 3f64.sqrt(), 1.0 / 3f64.sqrt(), 1.0 / 3f64.sqrt()]);
|
||||||
|
assert_ulps_eq!(expected, q.slerp(r, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_quarter() {
|
||||||
|
let q = Quaternion::from([-0.5, 0.5, 0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([
|
||||||
|
-0.2588190451025208,
|
||||||
|
0.5576775358252053,
|
||||||
|
0.5576775358252053,
|
||||||
|
0.5576775358252053,
|
||||||
|
]);
|
||||||
|
assert_ulps_eq!(expected, q.slerp(r, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_zero_dot() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, 0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([
|
||||||
|
-0.27059805007309845,
|
||||||
|
-0.27059805007309845,
|
||||||
|
0.6532814824381883,
|
||||||
|
0.6532814824381883,
|
||||||
|
]);
|
||||||
|
assert_ulps_eq!(expected, q.slerp(r, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_negative_dot() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, -0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([
|
||||||
|
-0.5576775358252053,
|
||||||
|
-0.5576775358252053,
|
||||||
|
-0.5576775358252053,
|
||||||
|
0.2588190451025208
|
||||||
|
]);
|
||||||
|
assert_ulps_eq!(expected, q.slerp(r, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_opposite() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, -0.5, -0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
assert_ulps_eq!(q, q.slerp(r, 0.25));
|
||||||
|
assert_ulps_eq!(q, q.slerp(r, 0.75));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_extrapolate() {
|
||||||
|
let q = Quaternion::from([-0.5, -0.5, -0.5, 0.5]);
|
||||||
|
let r = Quaternion::from([0.5, 0.5, 0.5, 0.5]);
|
||||||
|
|
||||||
|
let expected = Quaternion::from([0.0, 0.0, 0.0, 1.0]);
|
||||||
|
assert_ulps_eq!(expected, q.slerp(r, -1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slerp_regression() {
|
||||||
|
let a = Quaternion::<f32>::new(0.00052311074, 0.9999999, 0.00014682197, -0.000016342687);
|
||||||
|
let b = Quaternion::<f32>::new(0.019973433, -0.99980056, -0.00015678025, 0.000013882192);
|
||||||
|
|
||||||
|
assert_ulps_eq!(a.slerp(b, 0.5).magnitude(), 1.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,7 +205,10 @@ pub trait MetricSpace: Sized {
|
||||||
fn distance2(self, other: Self) -> Self::Metric;
|
fn distance2(self, other: Self) -> Self::Metric;
|
||||||
|
|
||||||
/// The distance between two values.
|
/// The distance between two values.
|
||||||
fn distance(self, other: Self) -> Self::Metric where Self::Metric: Float {
|
fn distance(self, other: Self) -> Self::Metric
|
||||||
|
where
|
||||||
|
Self::Metric: Float,
|
||||||
|
{
|
||||||
Float::sqrt(Self::distance2(self, other))
|
Float::sqrt(Self::distance2(self, other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,14 +223,17 @@ pub trait MetricSpace: Sized {
|
||||||
pub trait InnerSpace: VectorSpace
|
pub trait InnerSpace: VectorSpace
|
||||||
where
|
where
|
||||||
// FIXME: Ugly type signatures - blocked by rust-lang/rust#24092
|
// FIXME: Ugly type signatures - blocked by rust-lang/rust#24092
|
||||||
Self: MetricSpace<Metric = <Self as VectorSpace>::Scalar>
|
Self: MetricSpace<Metric = <Self as VectorSpace>::Scalar>,
|
||||||
{
|
{
|
||||||
/// Vector dot (or inner) product.
|
/// Vector dot (or inner) product.
|
||||||
fn dot(self, other: Self) -> Self::Scalar;
|
fn dot(self, other: Self) -> Self::Scalar;
|
||||||
|
|
||||||
/// Returns `true` if the vector is perpendicular (at right angles) to the
|
/// Returns `true` if the vector is perpendicular (at right angles) to the
|
||||||
/// other vector.
|
/// other vector.
|
||||||
fn is_perpendicular(self, other: Self) -> bool where Self::Scalar: approx::UlpsEq {
|
fn is_perpendicular(self, other: Self) -> bool
|
||||||
|
where
|
||||||
|
Self::Scalar: approx::UlpsEq,
|
||||||
|
{
|
||||||
ulps_eq!(Self::dot(self, other), &Self::Scalar::zero())
|
ulps_eq!(Self::dot(self, other), &Self::Scalar::zero())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +248,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the angle between two vectors in radians.
|
/// Returns the angle between two vectors in radians.
|
||||||
fn angle(self, other: Self) -> Rad<Self::Scalar> where Self::Scalar: BaseFloat {
|
fn angle(self, other: Self) -> Rad<Self::Scalar>
|
||||||
|
where
|
||||||
|
Self::Scalar: BaseFloat,
|
||||||
|
{
|
||||||
Rad::acos(Self::dot(self, other) / (self.magnitude() * other.magnitude()))
|
Rad::acos(Self::dot(self, other) / (self.magnitude() * other.magnitude()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,19 +265,28 @@ where
|
||||||
|
|
||||||
/// The distance from the tail to the tip of the vector.
|
/// The distance from the tail to the tip of the vector.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn magnitude(self) -> Self::Scalar where Self::Scalar: Float {
|
fn magnitude(self) -> Self::Scalar
|
||||||
|
where
|
||||||
|
Self::Scalar: Float,
|
||||||
|
{
|
||||||
Float::sqrt(self.magnitude2())
|
Float::sqrt(self.magnitude2())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a vector with the same direction, but with a magnitude of `1`.
|
/// Returns a vector with the same direction, but with a magnitude of `1`.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn normalize(self) -> Self where Self::Scalar: Float {
|
fn normalize(self) -> Self
|
||||||
|
where
|
||||||
|
Self::Scalar: Float,
|
||||||
|
{
|
||||||
self.normalize_to(Self::Scalar::one())
|
self.normalize_to(Self::Scalar::one())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a vector with the same direction and a given magnitude.
|
/// Returns a vector with the same direction and a given magnitude.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn normalize_to(self, magnitude: Self::Scalar) -> Self where Self::Scalar: Float {
|
fn normalize_to(self, magnitude: Self::Scalar) -> Self
|
||||||
|
where
|
||||||
|
Self::Scalar: Float,
|
||||||
|
{
|
||||||
self * (magnitude / self.magnitude())
|
self * (magnitude / self.magnitude())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -536,14 +554,20 @@ where
|
||||||
|
|
||||||
/// Test if this matrix is invertible.
|
/// Test if this matrix is invertible.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_invertible(&self) -> bool where Self::Scalar: approx::UlpsEq {
|
fn is_invertible(&self) -> bool
|
||||||
|
where
|
||||||
|
Self::Scalar: approx::UlpsEq,
|
||||||
|
{
|
||||||
ulps_ne!(self.determinant(), &Self::Scalar::zero())
|
ulps_ne!(self.determinant(), &Self::Scalar::zero())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if this matrix is the identity matrix. That is, it is diagonal
|
/// Test if this matrix is the identity matrix. That is, it is diagonal
|
||||||
/// and every element in the diagonal is one.
|
/// and every element in the diagonal is one.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_identity(&self) -> bool where Self: approx::UlpsEq {
|
fn is_identity(&self) -> bool
|
||||||
|
where
|
||||||
|
Self: approx::UlpsEq,
|
||||||
|
{
|
||||||
ulps_eq!(self, &Self::identity())
|
ulps_eq!(self, &Self::identity())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use num_traits::{Float, Bounded, NumCast};
|
use num_traits::{Bounded, Float, NumCast};
|
||||||
#[cfg(feature = "rand")]
|
#[cfg(feature = "rand")]
|
||||||
use rand::{
|
use rand::{
|
||||||
distributions::{Distribution, Standard},
|
distributions::{Distribution, Standard},
|
||||||
|
@ -539,7 +539,10 @@ impl<S: BaseNum> InnerSpace for Vector2<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn angle(self, other: Vector2<S>) -> Rad<S> where S: BaseFloat {
|
fn angle(self, other: Vector2<S>) -> Rad<S>
|
||||||
|
where
|
||||||
|
S: BaseFloat,
|
||||||
|
{
|
||||||
Rad::atan2(Self::perp_dot(self, other), Self::dot(self, other))
|
Rad::atan2(Self::perp_dot(self, other), Self::dot(self, other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -551,7 +554,10 @@ impl<S: BaseNum> InnerSpace for Vector3<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn angle(self, other: Vector3<S>) -> Rad<S> where S: BaseFloat {
|
fn angle(self, other: Vector3<S>) -> Rad<S>
|
||||||
|
where
|
||||||
|
S: BaseFloat,
|
||||||
|
{
|
||||||
Rad::atan2(self.cross(other).magnitude(), Self::dot(self, other))
|
Rad::atan2(self.cross(other).magnitude(), Self::dot(self, other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue