Skip to content

Commit

Permalink
[Math/Quaternion] Optimized the quaternion/vector multiplication
Browse files Browse the repository at this point in the history
- It allows a speedup of about 1.5x: https://quick-bench.com/q/S9_OOHC7hm0uthazzIKUqyBrLZE
  - Clang reports a speedup of 3x, but the assembly for the initial case is worse than GCC's

- It is based off https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/
  • Loading branch information
Razakhel committed Aug 28, 2024
1 parent c79a9eb commit 6c05159
Show file tree
Hide file tree
Showing 3 changed files with 12 additions and 16 deletions.
20 changes: 8 additions & 12 deletions include/RaZ/Math/Quaternion.inl
Original file line number Diff line number Diff line change
Expand Up @@ -169,24 +169,20 @@ constexpr Quaternion<T> Quaternion<T>::lerp(const Quaternion& quat, T currCoeff,

template <typename T>
constexpr Vec3<T> operator*(const Quaternion<T>& quat, const Vec3<T>& 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<T> vecQuat(0.f, vec.x(), vec.y(), vec.z());
vecQuat = quat * vecQuat * quat.conjugate();
return Vec3<T>(vecQuat.x(), vecQuat.y(), vecQuat.z());
const Vec3<T> quatVec(quat.x(), quat.y(), quat.z());
const Vec3<T> doubleQuatVecCross = static_cast<T>(2) * quatVec.cross(vec);
return vec + quat.w() * doubleQuatVecCross + quatVec.cross(doubleQuatVecCross);
}

template <typename T>
constexpr Vec3<T> operator*(const Vec3<T>& vec, const Quaternion<T>& 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<T> vecQuat(0.f, vec.x(), vec.y(), vec.z());
vecQuat = quat.conjugate() * vecQuat * quat;
return Vec3<T>(vecQuat.x(), vecQuat.y(), vecQuat.z());
const Vec3<T> quatVec(quat.x(), quat.y(), quat.z());
const Vec3<T> doubleQuatVecCross = static_cast<T>(2) * vec.cross(quatVec);
return vec + quat.w() * doubleQuatVecCross + doubleQuatVecCross.cross(quatVec);
}

} // namespace Raz
2 changes: 1 addition & 1 deletion include/RaZ/Math/Vector.inl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ constexpr Vector<T, Size> Vector<T, Size>::cross(const Vector& vec) const noexce

template <typename T, std::size_t Size>
constexpr Vector<T, Size> Vector<T, Size>::reflect(const Vector& normal) const noexcept {
static_assert(std::is_signed_v<T>, "Error: The cross product can only be computed with vectors of a signed type.");
static_assert(std::is_signed_v<T>, "Error: The reflected vector can only be computed with vectors of a signed type.");

return (*this - normal * static_cast<T>(dot(normal)) * 2);
}
Expand Down
6 changes: 3 additions & 3 deletions tests/src/RaZ/Math/Quaternion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 6c05159

Please sign in to comment.