diff --git a/src/Math/DualQuaternion.h b/src/Math/DualQuaternion.h index 9d3e79902..29aefc127 100644 --- a/src/Math/DualQuaternion.h +++ b/src/Math/DualQuaternion.h @@ -56,7 +56,7 @@ template class DualQuaternion: public Dual> { * @f] * @see rotationAngle(), rotationAxis(), Quaternion::rotation(), * Matrix4::rotation(), DualComplex::rotation(), Vector3::xAxis(), - * Vector3::yAxis(), Vector3::zAxis() + * Vector3::yAxis(), Vector3::zAxis(), Vector::isNormalized() */ inline static DualQuaternion rotation(Rad angle, const Vector3& normalizedAxis) { return {Quaternion::rotation(angle, normalizedAxis), {{}, T(0)}}; @@ -132,6 +132,18 @@ template class DualQuaternion: public Dual> { inline constexpr explicit DualQuaternion(const Vector3& vector): Dual>({}, {vector, T(0)}) {} #endif + /** + * @brief Whether the dual quaternion is normalized + * + * Dual quaternion is normalized if it has unit length: @f[ + * |\hat q|^2 = |\hat q| = 1 + \epsilon 0 + * @f] + * @see lengthSquared(), normalized() + */ + inline bool isNormalized() const { + return lengthSquared() == Dual(1); + } + /** * @brief Rotation angle of unit dual quaternion * @@ -240,7 +252,11 @@ template class DualQuaternion: public Dual> { return Math::sqrt(lengthSquared()); } - /** @brief Normalized dual quaternion (of unit length) */ + /** + * @brief Normalized dual quaternion (of unit length) + * + * @see isNormalized() + */ inline DualQuaternion normalized() const { return (*this)/length(); } @@ -264,10 +280,10 @@ template class DualQuaternion: public Dual> { * normalized. @f[ * \hat q^{-1} = \frac{\hat q^*}{|\hat q|^2} = \hat q^* * @f] - * @see inverted() + * @see isNormalized(), inverted() */ inline DualQuaternion invertedNormalized() const { - CORRADE_ASSERT(lengthSquared() == Dual(1), + CORRADE_ASSERT(isNormalized(), "Math::DualQuaternion::invertedNormalized(): dual quaternion must be normalized", {}); return quaternionConjugated(); } @@ -293,11 +309,12 @@ template class DualQuaternion: public Dual> { * quaternion is normalized. @f[ * v' = \hat q v \overline{\hat q^{-1}} = \hat q v \overline{\hat q^*} = \hat q ([\boldsymbol 0, 1] + \epsilon [\boldsymbol v, 0]) \overline{\hat q^*} * @f] - * @see DualQuaternion(const Vector3&), dual(), Matrix4::transformPoint(), - * Quaternion::transformVectorNormalized(), DualComplex::transformPointNormalized() + * @see isNormalized(), DualQuaternion(const Vector3&), dual(), + * Matrix4::transformPoint(), Quaternion::transformVectorNormalized(), + * DualComplex::transformPointNormalized() */ inline Vector3 transformPointNormalized(const Vector3& vector) const { - CORRADE_ASSERT(lengthSquared() == Dual(1), + CORRADE_ASSERT(isNormalized(), "Math::DualQuaternion::transformPointNormalized(): dual quaternion must be normalized", Vector3(std::numeric_limits::quiet_NaN())); return ((*this)*DualQuaternion(vector)*conjugated()).dual().vector(); diff --git a/src/Math/Quaternion.h b/src/Math/Quaternion.h index aa5614810..217c852cf 100644 --- a/src/Math/Quaternion.h +++ b/src/Math/Quaternion.h @@ -107,10 +107,10 @@ template class Quaternion { * Expects that both quaternions are normalized. @f[ * \theta = acos \left( \frac{p \cdot q}{|p| |q|} \right) = acos(p \cdot q) * @f] - * @see Complex::angle(), Vector::angle() + * @see isNormalized(), Complex::angle(), Vector::angle() */ inline static Rad angle(const Quaternion& normalizedA, const Quaternion& normalizedB) { - CORRADE_ASSERT(TypeTraits::equals(normalizedA.dot(), T(1)) && TypeTraits::equals(normalizedB.dot(), T(1)), + CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), "Math::Quaternion::angle(): quaternions must be normalized", Rad(std::numeric_limits::quiet_NaN())); return Rad(angleInternal(normalizedA, normalizedB)); } @@ -124,10 +124,10 @@ template class Quaternion { * Expects that both quaternions are normalized. @f[ * q_{LERP} = \frac{(1 - t) q_A + t q_B}{|(1 - t) q_A + t q_B|} * @f] - * @see slerp(), Math::lerp() + * @see isNormalized(), slerp(), Math::lerp() */ inline static Quaternion lerp(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { - CORRADE_ASSERT(TypeTraits::equals(normalizedA.dot(), T(1)) && TypeTraits::equals(normalizedB.dot(), T(1)), + CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), "Math::Quaternion::lerp(): quaternions must be normalized", Quaternion({}, std::numeric_limits::quiet_NaN())); return ((T(1) - t)*normalizedA + t*normalizedB).normalized(); @@ -144,10 +144,10 @@ template class Quaternion { * ~~~~~~~~~~ * \theta = acos \left( \frac{q_A \cdot q_B}{|q_A| \cdot |q_B|} \right) = acos(q_A \cdot q_B) * @f] - * @see lerp() + * @see isNormalized(), lerp() */ inline static Quaternion slerp(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { - CORRADE_ASSERT(TypeTraits::equals(normalizedA.dot(), T(1)) && TypeTraits::equals(normalizedB.dot(), T(1)), + CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), "Math::Quaternion::slerp(): quaternions must be normalized", Quaternion({}, std::numeric_limits::quiet_NaN())); T a = angleInternal(normalizedA, normalizedB); @@ -164,10 +164,10 @@ template class Quaternion { * @f] * @see rotationAngle(), rotationAxis(), DualQuaternion::rotation(), * Matrix4::rotation(), Complex::rotation(), Vector3::xAxis(), - * Vector3::yAxis(), Vector3::zAxis() + * Vector3::yAxis(), Vector3::zAxis(), Vector::isNormalized() */ inline static Quaternion rotation(Rad angle, const Vector3& normalizedAxis) { - CORRADE_ASSERT(TypeTraits::equals(normalizedAxis.dot(), T(1)), + CORRADE_ASSERT(normalizedAxis.isNormalized(), "Math::Quaternion::rotation(): axis must be normalized", {}); return {normalizedAxis*std::sin(T(angle)/2), std::cos(T(angle)/2)}; @@ -223,6 +223,18 @@ template class Quaternion { return !operator==(other); } + /** + * @brief Whether the quaternion is normalized + * + * Quaternion is normalized if it has unit length: @f[ + * |q|^2 = |q| = 1 + * @f] + * @see dot(), normalized() + */ + inline bool isNormalized() const { + return TypeTraits::equals(dot(), T(1)); + } + /** @brief %Vector part */ inline constexpr Vector3 vector() const { return _vector; } @@ -238,7 +250,7 @@ template class Quaternion { * @see rotationAxis(), rotation(), DualQuaternion::rotationAngle() */ inline Rad rotationAngle() const { - CORRADE_ASSERT(TypeTraits::equals(dot(), T(1)), + CORRADE_ASSERT(isNormalized(), "Math::Quaternion::rotationAngle(): quaternion must be normalized", Rad(std::numeric_limits::quiet_NaN())); return Rad(T(2)*std::acos(_scalar)); @@ -255,7 +267,7 @@ template class Quaternion { * @see rotationAngle(), rotation() */ inline Vector3 rotationAxis() const { - CORRADE_ASSERT(TypeTraits::equals(dot(), T(1)), + CORRADE_ASSERT(isNormalized(), "Math::Quaternion::rotationAxis(): quaternion must be normalized", {}); return _vector/std::sqrt(1-pow2(_scalar)); @@ -400,7 +412,7 @@ template class Quaternion { * with other values, because it doesn't compute the square root. @f[ * q \cdot q = \boldsymbol q_V \cdot \boldsymbol q_V + q_S^2 * @f] - * @see dot(const Quaternion&, const Quaternion&) + * @see isNormalized(), dot(const Quaternion&, const Quaternion&) */ inline T dot() const { return dot(*this, *this); @@ -413,12 +425,17 @@ template class Quaternion { * values. @f[ * |q| = \sqrt{q \cdot q} * @f] + * @see isNormalized() */ inline T length() const { return std::sqrt(dot()); } - /** @brief Normalized quaternion (of unit length) */ + /** + * @brief Normalized quaternion (of unit length) + * + * @see isNormalized() + */ inline Quaternion normalized() const { return (*this)/length(); } @@ -453,10 +470,10 @@ template class Quaternion { * normalized. @f[ * q^{-1} = \frac{q^*}{|q|^2} = q^* * @f] - * @see inverted() + * @see isNormalized(), inverted() */ inline Quaternion invertedNormalized() const { - CORRADE_ASSERT(TypeTraits::equals(dot(), T(1)), + CORRADE_ASSERT(isNormalized(), "Math::Quaternion::invertedNormalized(): quaternion must be normalized", Quaternion({}, std::numeric_limits::quiet_NaN())); return conjugated(); @@ -483,11 +500,11 @@ template class Quaternion { * is normalized. @f[ * v' = qvq^{-1} = qvq^* = q [\boldsymbol v, 0] q^* * @f] - * @see Quaternion(const Vector3&), vector(), Matrix4::transformVector(), + * @see isNormalized(), Quaternion(const Vector3&), vector(), Matrix4::transformVector(), * DualQuaternion::transformPointNormalized(), Complex::transformVector() */ inline Vector3 transformVectorNormalized(const Vector3& vector) const { - CORRADE_ASSERT(TypeTraits::equals(dot(), T(1)), + CORRADE_ASSERT(isNormalized(), "Math::Quaternion::transformVectorNormalized(): quaternion must be normalized", Vector3(std::numeric_limits::quiet_NaN())); return ((*this)*Quaternion(vector)*conjugated()).vector(); diff --git a/src/Math/Test/DualQuaternionTest.cpp b/src/Math/Test/DualQuaternionTest.cpp index cb5011d94..d8594fcfb 100644 --- a/src/Math/Test/DualQuaternionTest.cpp +++ b/src/Math/Test/DualQuaternionTest.cpp @@ -37,6 +37,8 @@ class DualQuaternionTest: public Corrade::TestSuite::Tester { void constructDefault(); void constructFromVector(); + void isNormalized(); + void constExpressions(); void lengthSquared(); @@ -72,6 +74,8 @@ DualQuaternionTest::DualQuaternionTest() { &DualQuaternionTest::constructDefault, &DualQuaternionTest::constructFromVector, + &DualQuaternionTest::isNormalized, + &DualQuaternionTest::constExpressions, &DualQuaternionTest::lengthSquared, @@ -109,6 +113,11 @@ void DualQuaternionTest::constructFromVector() { CORRADE_COMPARE(DualQuaternion(Vector3(1.0f, 2.0f, 3.0f)), DualQuaternion({{0.0f, 0.0f, 0.0f}, 1.0f}, {{1.0f, 2.0f, 3.0f}, 0.0f})); } +void DualQuaternionTest::isNormalized() { + CORRADE_VERIFY(!DualQuaternion({{1.0f, 2.0f, 3.0f}, 4.0f}, {}).isNormalized()); + CORRADE_VERIFY((DualQuaternion::rotation(Deg(23.0f), Vector3::xAxis())*DualQuaternion::translation({3.0f, 1.0f, -0.5f})).isNormalized()); +} + void DualQuaternionTest::constExpressions() { /* Default constructor */ constexpr DualQuaternion a; diff --git a/src/Math/Test/QuaternionTest.cpp b/src/Math/Test/QuaternionTest.cpp index 9f9bcc865..18b635da5 100644 --- a/src/Math/Test/QuaternionTest.cpp +++ b/src/Math/Test/QuaternionTest.cpp @@ -37,7 +37,9 @@ class QuaternionTest: public Corrade::TestSuite::Tester { void construct(); void constructDefault(); void constructFromVector(); + void compare(); + void isNormalized(); void constExpressions(); @@ -78,7 +80,9 @@ QuaternionTest::QuaternionTest() { addTests({&QuaternionTest::construct, &QuaternionTest::constructDefault, &QuaternionTest::constructFromVector, + &QuaternionTest::compare, + &QuaternionTest::isNormalized, &QuaternionTest::constExpressions, @@ -129,6 +133,11 @@ void QuaternionTest::compare() { CORRADE_VERIFY(Quaternion({4.0f, 2.0f, 3.0f}, -1.0f+TypeTraits::epsilon()*2) != Quaternion({4.0f, 2.0f, 3.0f}, -1.0f)); } +void QuaternionTest::isNormalized() { + CORRADE_VERIFY(!Quaternion({1.0f, 2.0f, 3.0f}, 4.0f).isNormalized()); + CORRADE_VERIFY(Quaternion::rotation(Deg(23.0f), Vector3::xAxis()).isNormalized()); +} + void QuaternionTest::constExpressions() { /* Default constructor */ constexpr Quaternion a;