From 51bc5203aeacf19460bcdd112dbb514659edc2b2 Mon Sep 17 00:00:00 2001 From: Hartmut Seichter Date: Mon, 15 Jul 2024 00:42:49 +0200 Subject: [PATCH] more restructuring including separated frustum and added quaternion slerp --- src/core/include/pw/core/axisangle.hpp | 2 +- src/core/include/pw/core/frustum.hpp | 65 +++++++++++++++++++ src/core/include/pw/core/matrix.hpp | 1 - src/core/include/pw/core/matrix_transform.hpp | 33 +--------- src/core/include/pw/core/quaternion.hpp | 60 ++++++++++++++++- src/core/tests/pwcore_test_quaternion.cpp | 13 +++- 6 files changed, 136 insertions(+), 38 deletions(-) create mode 100644 src/core/include/pw/core/frustum.hpp diff --git a/src/core/include/pw/core/axisangle.hpp b/src/core/include/pw/core/axisangle.hpp index 719cbb9..4bfe7a4 100644 --- a/src/core/include/pw/core/axisangle.hpp +++ b/src/core/include/pw/core/axisangle.hpp @@ -28,7 +28,7 @@ namespace pw { -template struct axisangle { +template struct axisangle final { using value_type = T; using axis_type = vector3; diff --git a/src/core/include/pw/core/frustum.hpp b/src/core/include/pw/core/frustum.hpp new file mode 100644 index 0000000..58c3922 --- /dev/null +++ b/src/core/include/pw/core/frustum.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 1999-2021 Hartmut Seichter + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#ifndef PW_CORE_FRUSTUM_HPP +#define PW_CORE_FRUSTUM_HPP + +#include + +namespace pw { + +template struct frustum final { + Scalar left{-1}, right{1}, bottom{-1}, top{1}, z_near{-1}, z_far{1}; + + static constexpr auto make_perspective_symmetric(Scalar fov_h_deg, + Scalar aspect_ratio, + Scalar z_near, + Scalar z_far) -> frustum { + const auto tangent_half{std::tan(pw::deg_to_rad(fov_h_deg / 2))}; + const auto top{tangent_half * z_near}; + const auto right{top * aspect_ratio}; + + return {.left = -right, + .right = right, + .bottom = -top, + .top = top, + .z_near = z_near, + .z_far = z_far}; + } + + static constexpr auto make_orthographic_symmetric(Scalar scale, + Scalar aspect_ratio, + Scalar z_near, + Scalar z_far) -> frustum { + return {.left = -scale, + .right = scale, + .bottom = -scale * aspect_ratio, + .top = scale * aspect_ratio, + .z_near = z_near, + .z_far = z_far}; + } +}; + +} // namespace pw + +#endif diff --git a/src/core/include/pw/core/matrix.hpp b/src/core/include/pw/core/matrix.hpp index 0a305e2..923aec1 100644 --- a/src/core/include/pw/core/matrix.hpp +++ b/src/core/include/pw/core/matrix.hpp @@ -23,7 +23,6 @@ #ifndef PW_CORE_MATRIX_HPP #define PW_CORE_MATRIX_HPP -#include #include #include #include diff --git a/src/core/include/pw/core/matrix_transform.hpp b/src/core/include/pw/core/matrix_transform.hpp index 5616ddf..cf170f2 100644 --- a/src/core/include/pw/core/matrix_transform.hpp +++ b/src/core/include/pw/core/matrix_transform.hpp @@ -26,41 +26,10 @@ #include #include #include +#include namespace pw { -template struct frustum final { - Scalar left{-1}, right{1}, bottom{-1}, top{1}, z_near{-1}, z_far{1}; - - static constexpr auto make_perspective_symmetric(Scalar fov_h_deg, - Scalar aspect_ratio, - Scalar z_near, - Scalar z_far) -> frustum { - const auto tangent_half{std::tan(pw::deg_to_rad(fov_h_deg / 2))}; - const auto top{tangent_half * z_near}; - const auto right{top * aspect_ratio}; - - return {.left = -right, - .right = right, - .bottom = -top, - .top = top, - .z_near = z_near, - .z_far = z_far}; - } - - static constexpr auto make_orthographic_symmetric(Scalar scale, - Scalar aspect_ratio, - Scalar z_near, - Scalar z_far) -> frustum { - return {.left = -scale, - .right = scale, - .bottom = -scale * aspect_ratio, - .top = scale * aspect_ratio, - .z_near = z_near, - .z_far = z_far}; - } -}; - struct matrix_transform { template diff --git a/src/core/include/pw/core/quaternion.hpp b/src/core/include/pw/core/quaternion.hpp index 80254ff..1e5893b 100644 --- a/src/core/include/pw/core/quaternion.hpp +++ b/src/core/include/pw/core/quaternion.hpp @@ -23,8 +23,8 @@ #ifndef PW_CORE_QUATERNION_HPP #define PW_CORE_QUATERNION_HPP -#include #include +#include #include #include @@ -83,6 +83,10 @@ template struct quaternion final { return conjugate() / this->norm(); } + constexpr auto dot(const quaternion& b) const noexcept -> Scalar { + return q_.dot(b.q_); + } + constexpr auto to_matrix() const noexcept -> matrix { const Scalar xx = x() * x(); @@ -119,9 +123,61 @@ template struct quaternion final { } static constexpr auto lerp(const quaternion& a, const quaternion& b, - const Scalar& t) -> quaternion { + const Scalar& t) -> quaternion { return {value_type::lerp(a.q_, b.q_, t).normalized()}; } + + /** + * @note: a and b need to be normalized + */ + static constexpr auto + slerp(const quaternion& a, const quaternion& b, const Scalar& t, + const Scalar& eps = Scalar{0.001}) -> quaternion { + + // Calculate angle between them. + const Scalar cos_half_theta{a.dot(b)}; + + // if qa=qb or qa=-qb then theta = 0 and we can return a + if (std::fabs(cos_half_theta) >= Scalar{1}) { + return a; + } + + // Calculate temporary values. + const Scalar half_theta{std::acos(cos_half_theta)}; + const Scalar sin_half_theta{ + std::sqrt(Scalar{1} - cos_half_theta * cos_half_theta)}; + + // if theta = 180 degrees then result is not fully defined + // we could rotate around any axis normal to a or b + const auto is_pi = std::fabs(sin_half_theta) < eps; + + // now do the lerp either halfway or across unit sphere + const Scalar ratio_a{is_pi ? Scalar{0.5} + : std::sin((Scalar{1} - t) * half_theta) / + sin_half_theta}; + + const Scalar ratio_b{is_pi ? Scalar{0.5} + : std::sin(t * half_theta) / sin_half_theta}; + return { + (a.x() * ratio_a + b.x() * ratio_b), // x + (a.y() * ratio_a + b.y() * ratio_b), // y + (a.z() * ratio_a + b.z() * ratio_b), // z + (a.w() * ratio_a + b.w() * ratio_b) // w + }; + } + + static constexpr auto + from_axisangle(const axisangle& aa) -> quaternion { + + const auto sin_half_angle{std::sin(aa.angle / Scalar{2})}; + + return { + aa.axis.x() * sin_half_angle, // x + aa.axis.y() * sin_half_angle, // y + aa.axis.z() * sin_half_angle, // z + std::cos(aa.angle / Scalar{2}) // w + }; + } }; // deduction guide for quaternion diff --git a/src/core/tests/pwcore_test_quaternion.cpp b/src/core/tests/pwcore_test_quaternion.cpp index 8492549..588e0d8 100644 --- a/src/core/tests/pwcore_test_quaternion.cpp +++ b/src/core/tests/pwcore_test_quaternion.cpp @@ -13,6 +13,10 @@ auto main() -> int { pw::debug::d() << "q1 = quaternion{1,2,3,4} -> \n" << pw::serialize::to_string(q1); + q1 = q1.normalized(); + pw::debug::d() << "q1 = quaternion{1,2,3,4}.normalized() -> \n" + << pw::serialize::to_string(q1); + auto q0_x_q1 = q0 * q1; pw::debug::d() << "q0 * q1 -> \n" << pw::serialize::to_string(q0_x_q1); @@ -28,10 +32,15 @@ auto main() -> int { pw::debug::d() << "q1.conjugate().inverse() -> \n" << pw::serialize::to_string(q1_conj_inv); - auto lerp_q0_q1_half = decltype(q0)::lerp(q0, q1, 0.5f); - pw::debug::d() << "quaternion::lerp(q0,q1,0.5) -> \n" + pw::debug::d() << "quaternion::dot(q0,q1) -> " << q0.dot(q1); + + auto lerp_q0_q1_half = decltype(q0)::lerp(q0, q1, 0.3f); + pw::debug::d() << "quaternion::lerp(q0,q1,0.3) -> \n" << pw::serialize::to_string(lerp_q0_q1_half); + auto slerp_q0_q1_half = decltype(q0)::slerp(q0, q1, 0.3f); + pw::debug::d() << "quaternion::slerp(q0,q1,0.3) -> \n" + << pw::serialize::to_string(slerp_q0_q1_half); return 0; }