Spaces:
Runtime error
Runtime error
| /* | |
| * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| namespace Math | |
| { | |
| inline Quaternion Quaternion::FromRotationBetweenNormalizedVectors(const Vector& from, const Vector& to) | |
| { | |
| ASSERT(from.IsNormalized3() && to.IsNormalized3()); | |
| Quaternion result; | |
| // Parallel vectors - return zero rotation | |
| Vector const dot = Vector::Dot3(from, to); | |
| if (dot.IsGreaterThanEqual4(Vector::OneMinusEpsilon)) | |
| { | |
| result = Quaternion::Identity; | |
| } | |
| // Opposite vectors - return 180 rotation around any orthogonal axis | |
| else if (dot.IsLessThanEqual4(Vector::EpsilonMinusOne)) | |
| { | |
| Float4 const fromValues = from.ToFloat4(); | |
| result = Quaternion(-fromValues.m_z, fromValues.m_y, fromValues.m_x, 0); | |
| result.Normalize(); | |
| } | |
| else // Calculate quaternion rotation | |
| { | |
| Vector const cross = Vector::Cross3(from, to); | |
| Vector Q = Vector::Select(cross, dot, Vector::Select0001); | |
| Q += Vector::Select(Vector::Zero, Q.Length4(), Vector::Select0001); | |
| result = Quaternion(Q); | |
| result.Normalize(); | |
| } | |
| return result; | |
| } | |
| inline Quaternion Quaternion::FromRotationBetweenNormalizedVectors(const Vector& from, const Vector& to, const Vector& fallbackRotationAxis) | |
| { | |
| ASSERT(from.IsNormalized3() && to.IsNormalized3()); | |
| Quaternion Q(NoInit); | |
| Vector rotationAxis = from.Cross3(to).GetNormalized3(); | |
| if (rotationAxis.GetLengthSquared3() == 0) | |
| { | |
| rotationAxis = fallbackRotationAxis; | |
| } | |
| float const dot = from.GetDot3(to); | |
| if (dot >= (1.0f - Math::Epsilon)) | |
| { | |
| Q = Quaternion::Identity; | |
| } | |
| else | |
| { | |
| float const angle = Math::ACos(dot); | |
| Q = Quaternion(rotationAxis, angle); | |
| } | |
| return Q; | |
| } | |
| inline Quaternion Quaternion::FromRotationBetweenVectors(const Vector& sourceVector, const Vector& targetVector) | |
| { | |
| return FromRotationBetweenNormalizedVectors( | |
| sourceVector.GetNormalized3(), | |
| targetVector.GetNormalized3()); | |
| } | |
| inline Quaternion Quaternion::NLerp(const Quaternion& from, const Quaternion& to, float T) | |
| { | |
| ASSERT(T >= 0.0f && T <= 1.0f); | |
| Quaternion adjustedFrom(from); | |
| // Ensure that the rotations are in the same direction | |
| if (Quaternion::Dot(from, to).IsLessThan4(Vector::Zero)) | |
| { | |
| adjustedFrom.Negate(); | |
| } | |
| Quaternion result(Vector::Lerp(adjustedFrom.ToVector(), to.ToVector(), T)); | |
| result.Normalize(); | |
| return result; | |
| } | |
| inline Quaternion Quaternion::SLerp(const Quaternion& from, const Quaternion& to, float T) | |
| { | |
| ASSERT(T >= 0.0f && T <= 1.0f); | |
| static SIMD::UIntMask const maskSign = { 0x80000000,0x00000000,0x00000000,0x00000000 }; | |
| static __m128 const oneMinusEpsilon = { 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f, 1.0f - 0.00001f }; | |
| Vector const VecT(T); | |
| Vector cosOmega = Quaternion::Dot(from, to); | |
| Vector control = cosOmega.LessThan(Vector::Zero); | |
| Vector sign = Vector::Select(Vector::One, Vector::NegativeOne, control); | |
| cosOmega = _mm_mul_ps(cosOmega, sign); | |
| control = cosOmega.LessThan(oneMinusEpsilon); | |
| Vector sinOmega = _mm_mul_ps(cosOmega, cosOmega); | |
| sinOmega = _mm_sub_ps(Vector::One, sinOmega); | |
| sinOmega = _mm_sqrt_ps(sinOmega); | |
| Vector omega = Vector::ATan2(sinOmega, cosOmega); | |
| Vector V01 = _mm_shuffle_ps(VecT, VecT, _MM_SHUFFLE(2, 3, 0, 1)); | |
| V01 = _mm_and_ps(V01, SIMD::g_maskXY00); | |
| V01 = _mm_xor_ps(V01, maskSign); | |
| V01 = _mm_add_ps(Vector::UnitX, V01); | |
| Vector S0 = _mm_mul_ps(V01, omega); | |
| S0 = Vector::Sin(S0); | |
| S0 = _mm_div_ps(S0, sinOmega); | |
| S0 = Vector::Select(V01, S0, control); | |
| Vector S1 = S0.GetSplatY(); | |
| S0 = S0.GetSplatX(); | |
| S1 = _mm_mul_ps(S1, sign); | |
| Vector result = _mm_mul_ps(from, S0); | |
| S1 = _mm_mul_ps(S1, to); | |
| result = _mm_add_ps(result, S1); | |
| return Quaternion(result); | |
| } | |
| inline Quaternion Quaternion::FastSLerp(const Quaternion& q0, const Quaternion& q1, float t) | |
| { | |
| // Precomputed constants | |
| constexpr float const mu = 1.85298109240830f; | |
| static Vector const u0123 = _mm_setr_ps(1.f / (1 * 3), 1.f / (2 * 5), 1.f / (3 * 7), 1.f / (4 * 9)); | |
| static Vector const u4567 = _mm_setr_ps(1.f / (5 * 11), 1.f / (6 * 13), 1.f / (7 * 15), mu / (8 * 17)); | |
| static Vector const v0123 = _mm_setr_ps(1.f / 3, 2.f / 5, 3.f / 7, 4.f / 9); | |
| static Vector const v4567 = _mm_setr_ps(5.f / 11, 6.f / 13, 7.f / 15, mu * 8 / 17); | |
| static Vector const vSignMask = _mm_set1_ps(-0.f); | |
| // Common code for computing the scalar coefficients of SLERP | |
| auto CalculateCoefficient = [](Vector vT, Vector xm1) | |
| { | |
| Vector const vTSquared = vT * vT; | |
| // ( b4, b5, b6, b7 ) = ( x-1 ) * ( u4 * t^2 - v4, u5 * t^2 - v5, u6 * t^2 - v6, u7 * t^2 - v7 ) | |
| Vector b4567 = Vector::MultiplySubtract(u4567, vTSquared, v4567); | |
| b4567 *= xm1; | |
| // ( b7, b7, b7, b7 ) | |
| Vector b = b4567.GetSplatW(); | |
| Vector c = b + Vector::One; | |
| // ( b6, b6, b6, b6 ) | |
| b = b4567.GetSplatZ(); | |
| c = Vector::MultiplyAdd(b, c, Vector::One); | |
| // ( b5, b5, b5, b5 ) | |
| b = b4567.GetSplatY(); | |
| c = Vector::MultiplyAdd(b, c, Vector::One); | |
| // ( b4, b4, b4, b4 ) | |
| b = b4567.GetSplatX(); | |
| c = Vector::MultiplyAdd(b, c, Vector::One); | |
| // ( b0, b1, b2, b3 ) = | |
| // ( x-1)*(u0* t^2-v0, u1 * t^2 -v1, u2* t^2-v2, u3* t^2-v3 ) | |
| Vector b0123 = Vector::MultiplySubtract(u0123, vTSquared, v0123); | |
| b0123 *= xm1; | |
| // ( b3, b3, b3, b3 ) | |
| b = b0123.GetSplatW(); | |
| c = Vector::MultiplyAdd(b, c, Vector::One); | |
| // ( b2, b2, b2, b2 ) | |
| b = b0123.GetSplatZ(); | |
| c = Vector::MultiplyAdd(b, c, Vector::One); | |
| // ( b1, b1, b1, b1 ) | |
| b = b0123.GetSplatY(); | |
| c = Vector::MultiplyAdd(b, c, Vector::One); | |
| // ( b0, b0, b0, b0 ) | |
| b = b0123.GetSplatX(); | |
| c = Vector::MultiplyAdd(b, c, Vector::One); | |
| c *= vT; | |
| return c; | |
| }; | |
| Vector x = Vector::Dot4(q0.m_data, q1.m_data); // cos ( theta ) in all components | |
| Vector sign = _mm_and_ps(vSignMask, x); | |
| x = _mm_xor_ps(sign, x); | |
| Vector localQ1 = _mm_xor_ps(sign, q1); | |
| Vector xm1 = x - Vector::One; | |
| Vector cT = CalculateCoefficient(Vector(t), xm1); | |
| Vector cD = CalculateCoefficient(Vector(1.0f - t), xm1); | |
| cT = cT * localQ1; | |
| Quaternion result(Vector::MultiplyAdd(cD, q0.m_data, cT)); | |
| return result; | |
| } | |
| inline Quaternion Quaternion::SQuad(const Quaternion& q0, const Quaternion& q1, const Quaternion& q2, const Quaternion& q3, float t) | |
| { | |
| ASSERT(t >= 0.0f && t <= 1.0f); | |
| Quaternion const q03 = Quaternion::SLerp(q0, q3, t); | |
| Quaternion const q12 = Quaternion::SLerp(q1, q2, t); | |
| t = (t - (t * t)) * 2; | |
| Quaternion const result = Quaternion::SLerp(q03, q12, t); | |
| return result; | |
| } | |
| inline Quaternion Quaternion::Delta(const Quaternion& from, const Quaternion& to) | |
| { | |
| return to * from.GetInverse(); | |
| } | |
| inline Vector Quaternion::Dot(const Quaternion& q0, const Quaternion& q1) | |
| { | |
| return Vector::Dot4(q0.m_data, q1.m_data); | |
| } | |
| inline Radians Quaternion::Distance(const Quaternion& q0, const Quaternion& q1) | |
| { | |
| float const dot = Math::Clamp(Dot(q0, q1).ToFloat(), -1.0f, 1.0f); | |
| return Radians(2 * Math::ACos(Math::Abs(dot))); | |
| } | |
| inline Quaternion::Quaternion(NoInit_t) | |
| { | |
| } | |
| inline Quaternion::Quaternion(IdentityInit_t) | |
| : m_data(Vector::UnitW.m_data) | |
| { | |
| } | |
| inline Quaternion::Quaternion(const Vector& v) | |
| : m_data(v.m_data) | |
| { | |
| } | |
| inline Quaternion::Quaternion(float ix, float iy, float iz, float iw) | |
| { | |
| m_data = _mm_set_ps(iw, iz, iy, ix); | |
| } | |
| inline Quaternion::Quaternion(const Float4& v) | |
| : Quaternion(v.m_x, v.m_y, v.m_z, v.m_w) | |
| { | |
| } | |
| inline Quaternion::Quaternion(const Vector& axis, Radians angle) | |
| { | |
| ASSERT(axis.IsNormalized3()); | |
| auto N = _mm_and_ps(axis, SIMD::g_maskXYZ0); | |
| N = _mm_or_ps(N, Vector::UnitW); | |
| auto scale = _mm_set_ps1(0.5f * (float)angle); | |
| Vector sine, cosine; | |
| Vector::SinCos(sine, cosine, scale); | |
| scale = _mm_and_ps(sine, SIMD::g_maskXYZ0); | |
| cosine = _mm_and_ps(cosine, SIMD::g_mask000W); | |
| scale = _mm_or_ps(scale, cosine); | |
| N = _mm_mul_ps(N, scale); | |
| m_data = N; | |
| } | |
| inline Quaternion::Quaternion(AxisAngle axisAngle) | |
| : Quaternion(Vector(axisAngle.m_axis), axisAngle.m_angle) | |
| { | |
| } | |
| inline Quaternion::Quaternion(const EulerAngles& eulerAngles) | |
| { | |
| auto const rotationX = Quaternion(Vector::UnitX, eulerAngles.m_x); | |
| auto const rotationY = Quaternion(Vector::UnitY, eulerAngles.m_y); | |
| auto const rotationZ = Quaternion(Vector::UnitZ, eulerAngles.m_z); | |
| // Rotation order is XYZ - all in global space, hence the order is reversed | |
| m_data = (rotationX * rotationY * rotationZ).GetNormalized().m_data; | |
| } | |
| inline Quaternion::Quaternion(Radians rotX, Radians rotY, Radians rotZ) | |
| : Quaternion(EulerAngles(rotX, rotY, rotZ)) | |
| { | |
| } | |
| inline Quaternion::operator __m128& () | |
| { | |
| return m_data; | |
| } | |
| inline Quaternion::operator const __m128& () const | |
| { | |
| return m_data; | |
| } | |
| inline Float4 Quaternion::ToFloat4() const | |
| { | |
| Float4 v; | |
| _mm_storeu_ps(&v.m_x, m_data); | |
| return v; | |
| } | |
| inline Vector Quaternion::ToVector() const | |
| { | |
| return Vector(m_data); | |
| } | |
| inline Vector Quaternion::Length() | |
| { | |
| return ToVector().Length4(); | |
| } | |
| inline float Quaternion::GetLength() const | |
| { | |
| return ToVector().GetLength4(); | |
| } | |
| inline Radians Quaternion::GetAngle() const | |
| { | |
| return Radians(2.0f * Math::ACos(GetW())); | |
| } | |
| inline AxisAngle Quaternion::ToAxisAngle() const | |
| { | |
| return AxisAngle(ToVector(), Radians(2.0f * Math::ACos(GetW()))); | |
| } | |
| inline Vector Quaternion::RotateVector(const Vector& vector) const | |
| { | |
| Quaternion const A(Vector::Select(Vector::Select1110, vector, Vector::Select1110)); | |
| Quaternion const result = GetConjugate() * A; | |
| return (result * *this).ToVector(); | |
| } | |
| inline Vector Quaternion::RotateVectorInverse(const Vector& vector) const | |
| { | |
| Quaternion const A(Vector::Select(Vector::Select1110, vector, Vector::Select1110)); | |
| Quaternion const result = *this * A; | |
| return (result * GetConjugate()).ToVector(); | |
| } | |
| inline Quaternion& Quaternion::Conjugate() | |
| { | |
| static __m128 const conj = { -1.0f, -1.0f, -1.0f, 1.0f }; | |
| m_data = _mm_mul_ps(*this, conj); | |
| return *this; | |
| } | |
| inline Quaternion Quaternion::GetConjugate() const | |
| { | |
| Quaternion q = *this; | |
| q.Conjugate(); | |
| return q; | |
| } | |
| inline Quaternion& Quaternion::Negate() | |
| { | |
| m_data = _mm_mul_ps(*this, Vector::NegativeOne); | |
| return *this; | |
| } | |
| inline Quaternion Quaternion::GetNegated() const | |
| { | |
| Quaternion q = *this; | |
| q.Negate(); | |
| return q; | |
| } | |
| inline Quaternion& Quaternion::Invert() | |
| { | |
| Vector const conjugate(GetConjugate().m_data); | |
| Vector const length = ToVector().Length4(); | |
| Vector const mask = length.LessThanEqual(Vector::Epsilon); | |
| Vector const result = conjugate / length; | |
| m_data = result.Select(result, Vector::Zero, mask); | |
| return *this; | |
| } | |
| inline Quaternion Quaternion::GetInverse() const | |
| { | |
| Quaternion q = *this; | |
| q.Invert(); | |
| return q; | |
| } | |
| inline Quaternion& Quaternion::Normalize() | |
| { | |
| m_data = ToVector().GetNormalized4().m_data; | |
| return *this; | |
| } | |
| inline Quaternion Quaternion::GetNormalized() const | |
| { | |
| Quaternion q = *this; | |
| q.Normalize(); | |
| return q; | |
| } | |
| inline Vector Quaternion::XAxis() const noexcept | |
| { | |
| const float x = _mm_cvtss_f32(m_data); | |
| const float y = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(1, 1, 1, 1))); | |
| const float z = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(2, 2, 2, 2))); | |
| const float w = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(3, 3, 3, 3))); | |
| const float s = 2.0f * w; | |
| const float x2 = 2.0f * x; | |
| return Vector( | |
| x2 * x + s * w - 1.0f, | |
| x2 * y + s * z, | |
| x2 * z + s * -y); | |
| } | |
| inline Vector Quaternion::YAxis() const noexcept | |
| { | |
| const float x = _mm_cvtss_f32(m_data); | |
| const float y = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(1, 1, 1, 1))); | |
| const float z = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(2, 2, 2, 2))); | |
| const float w = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(3, 3, 3, 3))); | |
| const float s = 2.0f * w; | |
| const float y2 = 2.0f * y; | |
| return Vector( | |
| y2 * x + s * -z, | |
| y2 * y + s * w - 1.0f, | |
| y2 * z + s * x); | |
| } | |
| inline Vector Quaternion::ZAxis() const noexcept | |
| { | |
| const float x = _mm_cvtss_f32(m_data); | |
| const float y = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(1, 1, 1, 1))); | |
| const float z = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(2, 2, 2, 2))); | |
| const float w = _mm_cvtss_f32( | |
| _mm_shuffle_ps(m_data, m_data, | |
| _MM_SHUFFLE(3, 3, 3, 3))); | |
| const float s = 2.0f * w; | |
| const float z2 = 2.0f * z; | |
| return Vector( | |
| x * z2 + s * y, | |
| y * z2 + s * -x, | |
| z * z2 + s * w - 1.0f); | |
| } | |
| inline Quaternion& Quaternion::MakeShortestPath() | |
| { | |
| // If we have a > 180 angle, negate | |
| // w < 0.0f is the same as dot( identity, q ) < 0 | |
| if (GetW() < 0.0f) | |
| { | |
| Negate(); | |
| } | |
| return *this; | |
| } | |
| inline Quaternion Quaternion::GetShortestPath() const | |
| { | |
| Quaternion sp = *this; | |
| sp.MakeShortestPath(); | |
| return sp; | |
| } | |
| inline Quaternion& Quaternion::NormalizeInaccurate() | |
| { | |
| *this = GetNormalizedInaccurate(); | |
| return *this; | |
| } | |
| inline Quaternion Quaternion::GetNormalizedInaccurate() const | |
| { | |
| __m128 vLengthSq = _mm_mul_ps(m_data, m_data); | |
| __m128 vTemp = _mm_shuffle_ps(vLengthSq, vLengthSq, _MM_SHUFFLE(3, 2, 3, 2)); | |
| vLengthSq = _mm_add_ps(vLengthSq, vTemp); | |
| vLengthSq = _mm_shuffle_ps(vLengthSq, vLengthSq, _MM_SHUFFLE(1, 0, 0, 0)); | |
| vTemp = _mm_shuffle_ps(vTemp, vLengthSq, _MM_SHUFFLE(3, 3, 0, 0)); | |
| vLengthSq = _mm_add_ps(vLengthSq, vTemp); | |
| vLengthSq = _mm_shuffle_ps(vLengthSq, vLengthSq, _MM_SHUFFLE(2, 2, 2, 2)); | |
| // Get the reciprocal and mul to perform the normalization | |
| Quaternion result; | |
| result.m_data = _mm_rsqrt_ps(vLengthSq); | |
| result.m_data = _mm_mul_ps(result.m_data, m_data); | |
| return result; | |
| } | |
| inline bool Quaternion::IsNormalized() const | |
| { | |
| return ToVector().IsNormalized4(); | |
| } | |
| inline bool Quaternion::IsIdentity() const | |
| { | |
| return ToVector().IsEqual3(Vector::UnitW); | |
| } | |
| inline Quaternion Quaternion::operator*(const Quaternion& rhs) const | |
| { | |
| static const __m128 controlWZYX = { 1.0f,-1.0f, 1.0f,-1.0f }; | |
| static const __m128 controlZWXY = { 1.0f, 1.0f,-1.0f,-1.0f }; | |
| static const __m128 controlYXWZ = { -1.0f, 1.0f, 1.0f,-1.0f }; | |
| // Copy to SSE registers and use as few as possible for x86 | |
| __m128 Q2X = rhs; | |
| __m128 Q2Y = rhs; | |
| __m128 Q2Z = rhs; | |
| __m128 vResult = rhs; | |
| // Splat with one instruction | |
| vResult = _mm_shuffle_ps(vResult, vResult, _MM_SHUFFLE(3, 3, 3, 3)); | |
| Q2X = _mm_shuffle_ps(Q2X, Q2X, _MM_SHUFFLE(0, 0, 0, 0)); | |
| Q2Y = _mm_shuffle_ps(Q2Y, Q2Y, _MM_SHUFFLE(1, 1, 1, 1)); | |
| Q2Z = _mm_shuffle_ps(Q2Z, Q2Z, _MM_SHUFFLE(2, 2, 2, 2)); | |
| // Retire Q1 and perform Q1*Q2W | |
| vResult = _mm_mul_ps(vResult, *this); | |
| __m128 Q1Shuffle = *this; | |
| // Shuffle the copies of Q1 | |
| Q1Shuffle = _mm_shuffle_ps(Q1Shuffle, Q1Shuffle, _MM_SHUFFLE(0, 1, 2, 3)); | |
| // Mul by Q1WZYX | |
| Q2X = _mm_mul_ps(Q2X, Q1Shuffle); | |
| Q1Shuffle = _mm_shuffle_ps(Q1Shuffle, Q1Shuffle, _MM_SHUFFLE(2, 3, 0, 1)); | |
| // Flip the signs on m_y and m_z | |
| Q2X = _mm_mul_ps(Q2X, controlWZYX); | |
| // Mul by Q1ZWXY | |
| Q2Y = _mm_mul_ps(Q2Y, Q1Shuffle); | |
| Q1Shuffle = _mm_shuffle_ps(Q1Shuffle, Q1Shuffle, _MM_SHUFFLE(0, 1, 2, 3)); | |
| // Flip the signs on m_z and m_w | |
| Q2Y = _mm_mul_ps(Q2Y, controlZWXY); | |
| // Mul by Q1YXWZ | |
| Q2Z = _mm_mul_ps(Q2Z, Q1Shuffle); | |
| vResult = _mm_add_ps(vResult, Q2X); | |
| // Flip the signs on m_x and m_w | |
| Q2Z = _mm_mul_ps(Q2Z, controlYXWZ); | |
| Q2Y = _mm_add_ps(Q2Y, Q2Z); | |
| vResult = _mm_add_ps(vResult, Q2Y); | |
| return Quaternion(vResult); | |
| } | |
| inline Quaternion& Quaternion::operator*=(const Quaternion& rhs) | |
| { | |
| *this = *this * rhs; | |
| return *this; | |
| } | |
| inline bool Quaternion::IsNearEqual(const Quaternion& rhs, Radians const threshold) const | |
| { | |
| return Quaternion::Distance(*this, rhs) <= threshold; | |
| } | |
| inline bool Quaternion::operator==(const Quaternion& rhs) const | |
| { | |
| return ToVector() == rhs.ToVector(); | |
| } | |
| inline bool Quaternion::operator!=(const Quaternion& rhs) const | |
| { | |
| return !operator==(rhs); | |
| } | |
| inline Vector Quaternion::GetSplatW() const | |
| { | |
| return _mm_shuffle_ps(m_data, m_data, _MM_SHUFFLE(3, 3, 3, 3)); | |
| } | |
| inline float Quaternion::GetW() const | |
| { | |
| auto vTemp = GetSplatW(); | |
| return _mm_cvtss_f32(vTemp); | |
| } | |
| } | |