diff --git a/include/RaZ/Math/Quaternion.inl b/include/RaZ/Math/Quaternion.inl index 26b63730..87b2b778 100644 --- a/include/RaZ/Math/Quaternion.inl +++ b/include/RaZ/Math/Quaternion.inl @@ -169,24 +169,20 @@ constexpr Quaternion Quaternion::lerp(const Quaternion& quat, T currCoeff, template constexpr Vec3 operator*(const Quaternion& quat, const Vec3& vec) noexcept { - // Quaternion/vector multiplications are supposed to be made with unit quaternions only, hence the conjugation instead of the inversion - // Most likely because of the error accumulation due to floating-point numbers, the norm of a supposedly unit quaternion is almost never very close to 1 - // That norm is thus not checked here; it may require changing to an actual inversion in the future to avoid further approximation errors + // Adapted from https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/ - Quaternion vecQuat(0.f, vec.x(), vec.y(), vec.z()); - vecQuat = quat * vecQuat * quat.conjugate(); - return Vec3(vecQuat.x(), vecQuat.y(), vecQuat.z()); + const Vec3 quatVec(quat.x(), quat.y(), quat.z()); + const Vec3 doubleQuatVecCross = static_cast(2) * quatVec.cross(vec); + return vec + quat.w() * doubleQuatVecCross + quatVec.cross(doubleQuatVecCross); } template constexpr Vec3 operator*(const Vec3& vec, const Quaternion& quat) noexcept { - // Quaternion/vector multiplications are supposed to be made with unit quaternions only, hence the conjugation instead of the inversion - // Most likely because of the error accumulation due to floating-point numbers, the norm of a supposedly unit quaternion is almost never very close to 1 - // That norm is thus not checked here; it may require changing to an actual inversion in the future to avoid further approximation errors + // Adapted from https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/ - Quaternion vecQuat(0.f, vec.x(), vec.y(), vec.z()); - vecQuat = quat.conjugate() * vecQuat * quat; - return Vec3(vecQuat.x(), vecQuat.y(), vecQuat.z()); + const Vec3 quatVec(quat.x(), quat.y(), quat.z()); + const Vec3 doubleQuatVecCross = static_cast(2) * vec.cross(quatVec); + return vec + quat.w() * doubleQuatVecCross + doubleQuatVecCross.cross(quatVec); } } // namespace Raz diff --git a/include/RaZ/Math/Vector.inl b/include/RaZ/Math/Vector.inl index 26c17df9..457e0fac 100644 --- a/include/RaZ/Math/Vector.inl +++ b/include/RaZ/Math/Vector.inl @@ -93,7 +93,7 @@ constexpr Vector Vector::cross(const Vector& vec) const noexce template constexpr Vector Vector::reflect(const Vector& normal) const noexcept { - static_assert(std::is_signed_v, "Error: The cross product can only be computed with vectors of a signed type."); + static_assert(std::is_signed_v, "Error: The reflected vector can only be computed with vectors of a signed type."); return (*this - normal * static_cast(dot(normal)) * 2); } diff --git a/tests/src/RaZ/Math/Quaternion.cpp b/tests/src/RaZ/Math/Quaternion.cpp index 0a9688b1..95c51ceb 100644 --- a/tests/src/RaZ/Math/Quaternion.cpp +++ b/tests/src/RaZ/Math/Quaternion.cpp @@ -143,9 +143,9 @@ TEST_CASE("Quaternion/vector multiplication", "[math]") { CHECK(quatRotY * pos == Raz::Vec3f(5.44976425f, pos.y(), 5.27581501f)); CHECK(quatRotZ * pos == Raz::Vec3f(-10.1741714f, 10.3481178f, pos.z())); - CHECK(pos * quatRotX == Raz::Vec3f(pos.x(), 15.6239338f, -4.89835596f)); - CHECK(pos * quatRotY == Raz::Vec3f(-5.27581596f, pos.y(), 5.4497633f)); - CHECK(pos * quatRotZ == Raz::Vec3f(10.3481188f, 10.1741686f, pos.z())); + CHECK_THAT(pos * quatRotX, IsNearlyEqualToVector(Raz::Vec3f(pos.x(), 15.6239338f, -4.89835596f), 0.000001f)); + CHECK_THAT(pos * quatRotY, IsNearlyEqualToVector(Raz::Vec3f(-5.27581596f, pos.y(), 5.4497633f))); + CHECK_THAT(pos * quatRotZ, IsNearlyEqualToVector(Raz::Vec3f(10.3481188f, 10.1741686f, pos.z()))); // Checking that the matrix multiplication behaves the same way