sandbox
Loading...
Searching...
No Matches
quaternion.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_MATH_QUATERNION_HPP_
3#define LIBSBX_MATH_QUATERNION_HPP_
4
5#include <cstddef>
6#include <concepts>
7#include <cmath>
8#include <type_traits>
9
10#include <yaml-cpp/yaml.h>
11
12#include <fmt/format.h>
13
17#include <libsbx/math/vector3.hpp>
18#include <libsbx/math/vector4.hpp>
19#include <libsbx/math/matrix4x4.hpp>
20#include <libsbx/math/angle.hpp>
21
22namespace sbx::math {
23
24template<floating_point Type>
26
27 template<floating_point Other>
29
30 template<floating_point Other>
32
33 template<floating_point Other>
35
36 template<floating_point Other>
38
39public:
40
41 using value_type = Type;
42 using reference = value_type&;
43 using const_reference = const value_type&;
44 using size_type = std::size_t;
45 using length_type = std::float_t;
49
50 inline static constexpr basic_quaternion identity{vector_type::zero, value_type{1}};
51
52 template<floating_point Other = value_type>
53 constexpr basic_quaternion(Other value = Other{0}) noexcept;
54
55 template<floating_point Complex = value_type, floating_point Scalar = value_type>
56 constexpr basic_quaternion(const vector_type_for<Complex>& complex, Scalar scalar) noexcept;
57
58 template<floating_point Other = value_type>
59 constexpr basic_quaternion(const vector_type_for<Other>& euler_angles) noexcept;
60
61 template<floating_point Other = value_type>
62 constexpr basic_quaternion(Other x, Other y, Other z, Other w) noexcept;
63
64 template<floating_point Complex = value_type, floating_point Scalar = value_type>
65 constexpr basic_quaternion(const vector_type_for<Complex>& axis, const basic_angle<Scalar>& angle) noexcept;
66
67 template<floating_point Other = value_type>
68 constexpr basic_quaternion(const basic_matrix4x4<Other>& matrix) noexcept;
69
70 template<floating_point Other = value_type>
71 constexpr basic_quaternion(const basic_matrix3x3<Other>& matrix) noexcept;
72
73 template<floating_point Other = value_type>
74 [[nodiscard]] static constexpr auto wxyz(Other w, Other x, Other y, Other z) noexcept -> basic_quaternion {
75 return basic_quaternion{x, y, z, w};
76 }
77
78 [[nodiscard]] static constexpr auto conjugate(const basic_quaternion& quaternion) noexcept -> basic_quaternion {
79 return basic_quaternion{-quaternion.complex(), quaternion.scalar()};
80 }
81
82 [[nodiscard]] static constexpr auto inverted(const basic_quaternion& quaternion) noexcept -> basic_quaternion {
83 const auto length_squared = dot(quaternion, quaternion);
84
85 if (length_squared < math::epsilonf) {
86 return basic_quaternion::identity;
87 }
88
89 const auto inverse_length_squared = 1.0f / length_squared;
90
91 const auto complex = vector_type{quaternion.x() * inverse_length_squared, quaternion.y() * inverse_length_squared, quaternion.z() * inverse_length_squared};
92 const auto scalar = quaternion.w() * inverse_length_squared;
93
94 return basic_quaternion{-complex, scalar};
95 }
96
97 [[nodiscard]] static constexpr auto normalized(const basic_quaternion& quaternion) noexcept -> basic_quaternion {
98 const auto length = quaternion.length();
99
100 if(length <= static_cast<value_type>(0)) {
101 return basic_quaternion{static_cast<value_type>(0), static_cast<value_type>(0), static_cast<value_type>(0), static_cast<value_type>(1)};
102 }
103
104 const auto one_over_length = static_cast<value_type>(1) / length;
105
106 return basic_quaternion{quaternion.x() * one_over_length, quaternion.y() * one_over_length, quaternion.z() * one_over_length, quaternion.w() * one_over_length};
107 }
108
109 [[nodiscard]] static constexpr auto dot(const basic_quaternion& lhs, const basic_quaternion& rhs) noexcept -> value_type {
110 return lhs.x() * rhs.x() + lhs.y() * rhs.y() + lhs.z() * rhs.z() + lhs.w() * rhs.w();
111 }
112
113 [[nodiscard]] static constexpr auto lerp(const basic_quaternion& start, const basic_quaternion& end, const value_type t) noexcept -> basic_quaternion {
114 return start * (1.0f - t) + end * t;
115 }
116
126 [[nodiscard]] static constexpr auto slerp(const basic_quaternion& x, basic_quaternion y, const value_type a) noexcept -> basic_quaternion {
127 utility::assert_that(a >= 0.0f && a <= 1.0f, "Interpolation factor out of bounds in quaternion slerp");
128
129 auto z = y;
130
131 auto cos_theta = dot(x, y);
132
133 // If cos_theta < 0, the interpolation will take the long way around the sphere.
134 // To fix this, one quat must be negated.
135 if(cos_theta < static_cast<value_type>(0)) {
136 z = -y;
137 cos_theta = -cos_theta;
138 }
139
140 // Perform a linear interpolation when cos_theta is close to 1 to avoid side effect of sin(angle) becoming a zero denominator
141 if(cos_theta > static_cast<value_type>(1) - math::epsilon_v<value_type>) {
142 // Linear interpolation
143 return basic_quaternion::wxyz(
144 math::mix(x.w(), z.w(), a),
145 math::mix(x.x(), z.x(), a),
146 math::mix(x.y(), z.y(), a),
147 math::mix(x.z(), z.z(), a)
148 );
149 } else {
150 // Essential Mathematics, page 467
151 const auto angle = std::acos(cos_theta);
152 return (std::sin((static_cast<value_type>(1) - a) * angle) * x + std::sin(a * angle) * z) / std::sin(angle);
153 }
154 }
155
156 [[constexpr]] static constexpr auto euler_angles(const basic_quaternion& quaternion) -> vector_type {
157 const auto x = quaternion.x();
158 const auto y = quaternion.y();
159 const auto z = quaternion.z();
160 const auto w = quaternion.w();
161
162 const auto sinr_cosp = 2.0f * (w * x + y * z);
163 const auto cosr_cosp = 1.0f - 2.0f * (x * x + y * y);
164 auto roll = std::atan2(sinr_cosp, cosr_cosp);
165
166 const auto sinp = 2.0f * (w * y - z * x);
167 auto pitch = 0.0f;
168
169 if (std::abs(sinp) >= 1.0f) {
170 pitch = std::copysign(3.14159265358979323846f / 2.0f, sinp);
171 } else {
172 pitch = std::asin(sinp);
173 }
174
175 const auto siny_cosp = 2.0f * (w * z + x * y);
176 const auto cosy_cosp = 1.0f - 2.0f * (y * y + z * z);
177
178 auto yaw = std::atan2(siny_cosp, cosy_cosp);
179
180 return math::vector3{to_degrees(radian{roll}).value(), to_degrees(radian{pitch}).value(), to_degrees(radian{yaw}).value()};
181 }
182
183 template<floating_point Other = value_type>
184 constexpr auto operator+=(const basic_quaternion<Other>& other) noexcept -> basic_quaternion&;
185
186 template<floating_point Other = value_type>
187 constexpr auto operator-=(const basic_quaternion<Other>& other) noexcept -> basic_quaternion&;
188
189 template<floating_point Other = value_type>
190 constexpr auto operator*=(Other value) noexcept -> basic_quaternion&;
191
192 constexpr auto operator*(const vector_type& vector) const noexcept -> vector_type {
193 const auto t = vector_type::cross(_complex, vector) * 2.0f;
194
195 return vector + (t * _scalar) + vector3::cross(_complex, t);
196 }
197
198 template<floating_point Other = value_type>
199 constexpr auto operator*=(const basic_quaternion<Other>& other) noexcept -> basic_quaternion&;
200
201 template<floating_point Other = value_type>
202 constexpr auto operator/=(Other value) noexcept -> basic_quaternion&;
203
204 [[nodiscard]] constexpr auto x() noexcept -> reference;
205
206 [[nodiscard]] constexpr auto x() const noexcept -> const_reference;
207
208 [[nodiscard]] constexpr auto y() noexcept -> reference;
209
210 [[nodiscard]] constexpr auto y() const noexcept -> const_reference;
211
212 [[nodiscard]] constexpr auto z() noexcept -> reference;
213
214 [[nodiscard]] constexpr auto z() const noexcept -> const_reference;
215
216 [[nodiscard]] constexpr auto w() noexcept -> reference;
217
218 [[nodiscard]] constexpr auto w() const noexcept -> const_reference;
219
220 [[nodiscard]] constexpr auto complex() noexcept -> vector_type&;
221
222 [[nodiscard]] constexpr auto complex() const noexcept -> const vector_type&;
223
224 [[nodiscard]] constexpr auto scalar() noexcept -> reference;
225
226 [[nodiscard]] constexpr auto scalar() const noexcept -> const_reference;
227
228 [[nodiscard]] constexpr auto length_squared() const noexcept -> length_type;
229
230 [[nodiscard]] constexpr auto length() const noexcept -> length_type;
231
232 constexpr auto normalize() noexcept -> basic_quaternion&;
233
234private:
235
236 vector_type _complex;
237 value_type _scalar;
238
239}; // class basic_quaternion
240
241template<floating_point Lhs, floating_point Rhs>
242[[nodiscard]] constexpr auto operator==(const basic_quaternion<Lhs>& lhs, const basic_quaternion<Rhs>& rhs) noexcept -> bool;
243
244template<floating_point Lhs, floating_point Rhs>
245[[nodiscard]] constexpr auto operator+(basic_quaternion<Lhs> lhs, const basic_quaternion<Rhs>& rhs) noexcept -> basic_quaternion<Lhs>;
246
247template<floating_point Lhs, floating_point Rhs>
248[[nodiscard]] constexpr auto operator-(basic_quaternion<Lhs> lhs, const basic_quaternion<Rhs>& rhs) noexcept -> basic_quaternion<Lhs>;
249
250template<floating_point Type>
251[[nodiscard]] constexpr auto operator-(basic_quaternion<Type> quaternion) noexcept -> basic_quaternion<Type>;
252
253template<floating_point Lhs, floating_point Rhs>
254[[nodiscard]] constexpr auto operator*(basic_quaternion<Lhs> lhs, Rhs rhs) noexcept -> basic_quaternion<Lhs>;
255
256template<floating_point Lhs, floating_point Rhs>
257[[nodiscard]] constexpr auto operator*(Lhs lhs, basic_quaternion<Rhs> rhs) noexcept -> basic_quaternion<Rhs>;
258
259template<floating_point Lhs, floating_point Rhs>
260[[nodiscard]] constexpr auto operator*(basic_quaternion<Lhs> lhs, const basic_quaternion<Rhs>& rhs) noexcept -> basic_quaternion<Lhs>;
261
262template<floating_point Lhs, floating_point Rhs>
263[[nodiscard]] constexpr auto operator/(basic_quaternion<Lhs> lhs, Rhs rhs) noexcept -> basic_quaternion<Lhs>;
264
266using quaternionf = basic_quaternion<std::float_t>;
267
269using quaternion = quaternionf;
270
271} // namespace sbx::math
272
273template<sbx::math::floating_point Type>
274struct std::hash<sbx::math::basic_quaternion<Type>> {
275
276 auto operator()(const sbx::math::basic_quaternion<Type>& quaternion) const noexcept -> std::size_t;
277
278}; // struct std::hash
279
280template<sbx::math::floating_point Type>
281struct YAML::convert<sbx::math::basic_quaternion<Type>> {
282
283 static auto encode(const sbx::math::basic_quaternion<Type>& quaternion) -> YAML::Node;
284
285 static auto decode(const YAML::Node& node, sbx::math::basic_quaternion<Type>& quaternion) -> bool;
286
287}; // struct YAML::convert
288
289template<sbx::math::floating_point Type>
290auto operator<<(YAML::Emitter& out, const sbx::math::basic_quaternion<Type>& quaternion) -> YAML::Emitter& {
291 return out << YAML::convert<sbx::math::basic_quaternion<Type>>::encode(quaternion);
292}
293
294template<sbx::math::floating_point Type>
295struct fmt::formatter<sbx::math::basic_quaternion<Type>> {
296
297 template<typename ParseContext>
298 constexpr auto parse(ParseContext& context) -> decltype(context.begin());
299
300 template<typename FormatContext>
301 auto format(const sbx::math::basic_quaternion<Type>& quaternion, FormatContext& context) -> decltype(context.out());
302
303}; // struct fmt::formatter
304
305#include <libsbx/math/quaternion.ipp>
306
307#endif // LIBSBX_MATH_QUATERNION_HPP_
Angle types and utilities.
constexpr auto to_degrees(const basic_radian< Type > &radian) noexcept -> basic_degree< Type >
Converts radians to degrees.
Definition: angle.ipp:412
Definition: tests.cpp:6
Unified angle type stored internally in radians.
Definition: angle.hpp:591
Definition: matrix3x3.hpp:26
Definition: matrix4x4.hpp:26
Definition: quaternion.hpp:25
static constexpr auto slerp(const basic_quaternion &x, basic_quaternion y, const value_type a) noexcept -> basic_quaternion
Spherical linear interpolation between two quaternions.
Definition: quaternion.hpp:126
Definition: vector3.hpp:23
Concept for scalar numeric types.
Definition: concepts.hpp:150
Mathematical constants.
Generic math algorithms and helpers.
Core numeric concepts and type traits.