diff --git a/doc/transformations.dox b/doc/transformations.dox index 9a95ad7e1d..a29b258f20 100644 --- a/doc/transformations.dox +++ b/doc/transformations.dox @@ -263,9 +263,11 @@ Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp( Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::lerpShortestPath()" Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" -Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()" +Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerpShortestPath()" Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::slerpShortestPath()" +Spherical linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::slerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::slerp()" +Spherical linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::slerpShortestPath()" Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()", \n @ref Math::sclerpShortestPath(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerpShortestPath()" Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" diff --git a/src/Magnum/Math/CubicHermite.h b/src/Magnum/Math/CubicHermite.h index b5ed30df2e..cd088bad92 100644 --- a/src/Magnum/Math/CubicHermite.h +++ b/src/Magnum/Math/CubicHermite.h @@ -316,6 +316,10 @@ Equivalent to calling @ref lerp(const T&, const T&, U) on @ref CubicHermite::point() extracted from both @p a and @p b. @see @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref lerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref slerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref slerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), @ref select(const CubicHermite&, const CubicHermite&, U), @ref splerp(const CubicHermite&, const CubicHermite&, U), @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), @@ -337,6 +341,7 @@ normalized complex number in both @p a and @p b. @see @ref Complex::isNormalized(), @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), @ref select(const CubicHermite&, const CubicHermite&, U), + @ref slerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) */ template Complex lerp(const CubicHermiteComplex& a, const CubicHermiteComplex& b, T t) { @@ -352,14 +357,86 @@ on @ref CubicHermite::point() extracted from @p a and @p b. Compared to normalization step after. Expects that @ref CubicHermite::point() is a normalized quaternion in both @p a and @p b. @see @ref Quaternion::isNormalized(), + @ref lerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), @ref select(const CubicHermite&, const CubicHermite&, U), + @ref slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) */ template Quaternion lerp(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { return lerp(a.point(), b.point(), t); } +/** @relatesalso CubicHermite +@brief Linear shortest-path interpolation of two cubic Hermite quaternions + +Equivalent to calling @ref lerpShortestPath(const Quaternion&, const Quaternion&, T) +on @ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized quaternion in both @p a and @p b. + +Note that rotations interpolated with this function may go along a completely +different path compared to @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T). +Use @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +for behavior that is consistent with spline interpolation. +@see @ref Quaternion::isNormalized(), + @ref slerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template Quaternion lerpShortestPath(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { + return lerpShortestPath(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Spherical linear interpolation of two cubic Hermite complex numbers + +Equivalent to calling @ref slerp(const Complex&, const Complex&, T) on +@ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized complex number in both @p a and @p b. +@see @ref Complex::isNormalized(), + @ref slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) + */ +template inline Complex slerp(const CubicHermiteComplex& a, const CubicHermiteComplex& b, T t) { + return slerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Spherical linear interpolation of two cubic Hermite quaternions + +Equivalent to calling @ref slerp(const Quaternion&, const Quaternion&, T) +on @ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized complex number in both @p a and @p b. +@see @ref Quaternion::isNormalized(), + @ref slerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref slerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) + */ +template inline Quaternion slerp(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { + return slerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Spherical linear shortest-path interpolation of two cubic Hermite quaternions + +Equivalent to calling @ref slerpShortestPath(const Quaternion&, const Quaternion&, T) +on @ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized quaternion in both @p a and @p b. + +Note that rotations interpolated with this function may go along a completely +different path compared to @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T). +Use @ref slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +for spherical linear interpolation with behavior that is consistent with spline +interpolation. +@see @ref Quaternion::isNormalized(), + @ref lerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template Quaternion slerpShortestPath(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { + return slerpShortestPath(a.point(), b.point(), t); +} + /** @relatesalso CubicHermite @brief Spline interpolation of two cubic Hermite points @param a First spline point @@ -402,7 +479,8 @@ Expects that @ref CubicHermite::point() is a normalized complex number in both @see @ref Complex::isNormalized(), @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), @ref select(const CubicHermite&, const CubicHermite&, U), - @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref slerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) */ template Complex splerp(const CubicHermiteComplex& a, const CubicHermiteComplex& b, T t) { CORRADE_ASSERT(a.point().isNormalized() && b.point().isNormalized(), @@ -431,7 +509,8 @@ and @p b. @see @ref Quaternion::isNormalized(), @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), @ref select(const CubicHermite&, const CubicHermite&, U), - @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) */ template Quaternion splerp(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { CORRADE_ASSERT(a.point().isNormalized() && b.point().isNormalized(), diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 86edc36ef6..6112fed47a 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -89,7 +89,8 @@ Expects that both quaternions are normalized. @f[ @f] Note that this function does not check for shortest path interpolation, see -@ref lerpShortestPath() for an alternative. +@ref lerpShortestPath(const Quaternion&, const Quaternion&, T) for an +alternative. @see @ref Quaternion::isNormalized(), @ref slerp(const Quaternion&, const Quaternion&, T), @ref sclerp(), @ref lerp(const T&, const T&, U), @@ -122,7 +123,9 @@ both quaternions are normalized. @f[ q_{LERP} & = & \cfrac{(1 - t) q'_A + t q_B}{|(1 - t) q'_A + t q_B|} \end{array} @f] -@see @ref Quaternion::isNormalized(), @ref slerpShortestPath(), +@see @ref Quaternion::isNormalized(), + @ref slerpShortestPath(const Quaternion&, const Quaternion&, T), + @ref lerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) @ref sclerpShortestPath() */ template inline Quaternion lerpShortestPath(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { @@ -153,9 +156,11 @@ otherwise, the interpolation is performed as: @f[ @f] Note that this function does not check for shortest path interpolation, see -@ref slerpShortestPath() for an alternative. +@ref slerpShortestPath(const Quaternion&, const Quaternion&, T) for an +alternative. @see @ref Quaternion::isNormalized(), @ref lerp(const Quaternion&, const Quaternion&, T), - @ref slerp(const Complex&, const Complex&, T), @ref sclerp() + @ref slerp(const Complex&, const Complex&, T), @ref sclerp(), + @ref slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) */ template inline Quaternion slerp(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), @@ -198,8 +203,10 @@ otherwise, the interpolation is performed as: @f[ q_{SLERP} & = & \cfrac{\sin((1 - t) \theta) q'_A + \sin(t \theta) q_B}{\sin(\theta)} \end{array} @f] -@see @ref Quaternion::isNormalized(), @ref lerpShortestPath(), - @ref sclerpShortestPath() +@see @ref Quaternion::isNormalized(), + @ref lerpShortestPath(const Quaternion&, const Quaternion&, T), + @ref slerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref sclerpShortestPath() */ template inline Quaternion slerpShortestPath(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), diff --git a/src/Magnum/Math/Test/CubicHermiteTest.cpp b/src/Magnum/Math/Test/CubicHermiteTest.cpp index c1ad7a9031..0555f115d3 100644 --- a/src/Magnum/Math/Test/CubicHermiteTest.cpp +++ b/src/Magnum/Math/Test/CubicHermiteTest.cpp @@ -94,6 +94,15 @@ struct CubicHermiteTest: Corrade::TestSuite::Tester { void lerpComplexNotNormalized(); void lerpQuaternion(); void lerpQuaternionNotNormalized(); + void lerpQuaternionShortestPath(); + void lerpQuaternionShortestPathNotNormalized(); + + void slerpComplex(); + void slerpComplexNotNormalized(); + void slerpQuaternion(); + void slerpQuaternionNotNormalized(); + void slerpQuaternionShortestPath(); + void slerpQuaternionShortestPathNotNormalized(); void splerpScalar(); void splerpVector(); @@ -168,6 +177,15 @@ CubicHermiteTest::CubicHermiteTest() { &CubicHermiteTest::lerpComplexNotNormalized, &CubicHermiteTest::lerpQuaternion, &CubicHermiteTest::lerpQuaternionNotNormalized, + &CubicHermiteTest::lerpQuaternionShortestPath, + &CubicHermiteTest::lerpQuaternionShortestPathNotNormalized, + + &CubicHermiteTest::slerpComplex, + &CubicHermiteTest::slerpComplexNotNormalized, + &CubicHermiteTest::slerpQuaternion, + &CubicHermiteTest::slerpQuaternionNotNormalized, + &CubicHermiteTest::slerpQuaternionShortestPath, + &CubicHermiteTest::slerpQuaternionShortestPathNotNormalized, &CubicHermiteTest::splerpScalar, &CubicHermiteTest::splerpVector, @@ -183,7 +201,10 @@ CubicHermiteTest::CubicHermiteTest() { &CubicHermiteTest::debugQuaternion}); } +using namespace Math::Literals; + typedef Math::Vector2 Vector2; +typedef Math::Vector3 Vector3; typedef Math::Complex Complex; typedef Math::Quaternion Quaternion; typedef Math::CubicBezier2D CubicBezier2D; @@ -821,6 +842,164 @@ void CubicHermiteTest::lerpQuaternionNotNormalized() { "Math::lerp(): quaternions must be normalized\n"); } +void CubicHermiteTest::lerpQuaternionShortestPath() { + /* Values from QuaternionTest::lerpShortestPath() */ + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + Quaternion::rotation(0.0_degf, Vector3::zAxis()), + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + Quaternion::rotation(225.0_degf, Vector3::zAxis()), + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + Quaternion lerp = Math::lerp(a, b, 0.25f); + Quaternion lerpShortestPath = Math::lerpShortestPath(a, b, 0.25f); + CORRADE_COMPARE(lerp.axis(), Vector3::zAxis()); + CORRADE_COMPARE(lerpShortestPath.axis(), Vector3::zAxis()); + CORRADE_COMPARE(lerp.angle(), 38.8848_degf); + CORRADE_COMPARE(lerpShortestPath.angle(), 329.448_degf); + + Quaternion expected{{0.0f, 0.0f, 0.26347f}, -0.964667f}; + CORRADE_COMPARE(lerpShortestPath, expected); + CORRADE_COMPARE(Math::lerpShortestPath(a.point(), b.point(), 0.25f), expected); +} + +void CubicHermiteTest::lerpQuaternionShortestPathNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + Math::lerpShortestPath(CubicHermiteQuaternion{}, {}, 0.3f); + + /* These will, tho */ + CubicHermiteQuaternion a{{}, Quaternion{}*2.0f, {}}; + Math::lerpShortestPath({}, a, 0.3f); + Math::lerpShortestPath(a, {}, 0.3f); + /* lerpShortestPath() is calling lerp(), so the message is from there */ + CORRADE_COMPARE(out.str(), + "Math::lerp(): quaternions must be normalized\n" + "Math::lerp(): quaternions must be normalized\n"); +} + +void CubicHermiteTest::slerpComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::slerp(a, b, 0.0f), a.point()); + CORRADE_COMPARE(Math::slerp(a, b, 1.0f), b.point()); + + Complex expected035{0.585564f, 0.810627f}; + CORRADE_COMPARE(Math::slerp(a, b, 0.35f), expected035); + CORRADE_COMPARE(Math::slerp(a.point(), b.point(), 0.35f), expected035); + CORRADE_VERIFY(Math::slerp(a, b, 0.35f).isNormalized()); + + Complex expected08{-0.520014f, 0.854159f}; + CORRADE_COMPARE(Math::slerp(a, b, 0.8f), expected08); + CORRADE_COMPARE(Math::slerp(a.point(), b.point(), 0.8f), expected08); + CORRADE_VERIFY(Math::slerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::slerpComplexNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + CORRADE_COMPARE(Math::slerp(CubicHermiteComplex{}, {}, 0.3f), Complex{}); + + /* These will, tho */ + CubicHermiteComplex a{{}, Complex{}*2.0f, {}}; + Math::slerp({}, a, 0.3f); + Math::slerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::slerp(): complex numbers must be normalized\n" + "Math::slerp(): complex numbers must be normalized\n"); +} + +void CubicHermiteTest::slerpQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-0.711568f, 0.391362f, 0.355784f}, 0.462519f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + CORRADE_COMPARE(Math::slerp(a, b, 0.0f), a.point()); + CORRADE_COMPARE(Math::slerp(a, b, 1.0f), b.point()); + + Quaternion expected035{{0.308542f, 0.265288f, 0.790272f}, 0.458142f}; + CORRADE_COMPARE(Math::slerp(a, b, 0.35f), expected035); + CORRADE_COMPARE(Math::slerp(a.point(), b.point(), 0.35f), expected035); + CORRADE_VERIFY(Math::slerp(a, b, 0.35f).isNormalized()); + + Quaternion expected08{{-0.442885f, 0.410928f, 0.584814f}, 0.541279f}; + CORRADE_COMPARE(Math::slerp(a, b, 0.8f), expected08); + CORRADE_COMPARE(Math::slerp(a.point(), b.point(), 0.8f), expected08); + CORRADE_VERIFY(Math::slerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::slerpQuaternionNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + Math::slerp(CubicHermiteQuaternion{}, {}, 0.3f); + + /* These will, tho */ + CubicHermiteQuaternion a{{}, Quaternion{}*2.0f, {}}; + Math::slerp({}, a, 0.3f); + Math::slerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::slerp(): quaternions must be normalized\n" + "Math::slerp(): quaternions must be normalized\n"); +} + +void CubicHermiteTest::slerpQuaternionShortestPath() { + /* Values from QuaternionTest::slerpShortestPath() */ + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + Quaternion::rotation(0.0_degf, Vector3::zAxis()), + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + Quaternion::rotation(225.0_degf, Vector3::zAxis()), + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + Quaternion slerp = Math::slerp(a, b, 0.25f); + Quaternion slerpShortestPath = Math::slerpShortestPath(a, b, 0.25f); + CORRADE_COMPARE(slerp.axis(), Vector3::zAxis()); + CORRADE_COMPARE(slerpShortestPath.axis(), Vector3::zAxis()); + CORRADE_COMPARE(slerp.angle(), 56.25_degf); + CORRADE_COMPARE(slerpShortestPath.angle(), 326.25_degf); + CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.471397f}, 0.881921f})); + + Quaternion expected{{0.0f, 0.0f, 0.290285f}, -0.95694f}; + CORRADE_COMPARE(slerpShortestPath, expected); + CORRADE_COMPARE(Math::slerpShortestPath(a.point(), b.point(), 0.25f), expected); +} + +void CubicHermiteTest::slerpQuaternionShortestPathNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + Math::slerpShortestPath(CubicHermiteQuaternion{}, {}, 0.3f); + + /* These will, tho */ + CubicHermiteQuaternion a{{}, Quaternion{}*2.0f, {}}; + Math::slerpShortestPath({}, a, 0.3f); + Math::slerpShortestPath(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::slerpShortestPath(): quaternions must be normalized\n" + "Math::slerpShortestPath(): quaternions must be normalized\n"); +} + void CubicHermiteTest::splerpScalar() { CubicHermite1D a{2.0f, 3.0f, -1.0f}; CubicHermite1D b{5.0f, -2.0f, 1.5f};