Move Vectormath main
C++20 game and graphics mathematics
Loading...
Searching...
No Matches
Rotation3.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <optional>
6#include <type_traits>
7
8#include <mv/math/Angles.hpp>
9#include <mv/math/Quat.hpp>
11
12namespace mv::math
13{
14 enum class EulerOrder
15 {
16 XYZ,
17 XZY,
18 YXZ,
19 YZX,
20 ZXY,
21 ZYX
22 };
23
24 template <typename T>
25 requires std::is_floating_point_v<T>
33
36
37 // Quaternion equations follow Szeliski, MSR-TR-2004-92, eqs. 22/26/28;
38 // conventions and results are cross-checked against RTM 2.3.1 (MIT).
39 template <typename T>
40 requires std::is_floating_point_v<T>
42 {
43 public:
44 using Component = T;
45
46 constexpr Rotation3() noexcept = default;
47
49 {
50 return Rotation3(Quat<T>(), UncheckedTag{});
51 }
52
53 [[nodiscard]] static std::optional<Rotation3> TryFrom(
54 const Quat<T>& value) noexcept
55 {
56 const auto normalized = TryNormalize(value);
57 if (!normalized)
58 {
59 return std::nullopt;
60 }
61 return Rotation3(*normalized, UncheckedTag{});
62 }
63
65 Radians<T> angle) noexcept
66 {
67 const T halfAngle = angle.Value() * T(0.5);
68 const T sine = std::sin(halfAngle);
69 const T cosine = std::cos(halfAngle);
70 const Vec3<T>& vector = axis.Vector();
71 return Rotation3(Quat<T>(vector.X() * sine, vector.Y() * sine,
72 vector.Z() * sine, cosine),
73 UncheckedTag{});
74 }
75
76 // Euler order names the application order to a local vector. This is
77 // intentionally explicit instead of inheriting an engine-specific
78 // implicit convention. Composition is cross-checked against GLM's MIT
79 // quaternion multiplication convention.
81 const EulerAngles3<T>& angles) noexcept
82 {
86 const auto apply = [](const Rotation3& first,
87 const Rotation3& second,
88 const Rotation3& third) noexcept
89 {
90 return third * second * first;
91 };
92 switch (angles.Order)
93 {
94 case EulerOrder::XYZ:
95 return apply(x, y, z);
96 case EulerOrder::XZY:
97 return apply(x, z, y);
98 case EulerOrder::YXZ:
99 return apply(y, x, z);
100 case EulerOrder::YZX:
101 return apply(y, z, x);
102 case EulerOrder::ZXY:
103 return apply(z, x, y);
104 case EulerOrder::ZYX:
105 return apply(z, y, x);
106 }
107 return Identity();
108 }
109
110 [[nodiscard]] static std::optional<Rotation3> TryFromOrthonormalAxes(
111 const Direction3<T>& axisX,
112 const Direction3<T>& axisY,
113 const Direction3<T>& axisZ,
114 T orthogonalityTolerance = static_cast<T>(1.0e-5)) noexcept
115 {
116 const Vec3<T>& x = axisX.Vector();
117 const Vec3<T>& y = axisY.Vector();
118 const Vec3<T>& z = axisZ.Vector();
119 const T tolerance = std::abs(orthogonalityTolerance);
120 if (std::abs(Dot(x, y)) > tolerance ||
121 std::abs(Dot(x, z)) > tolerance ||
122 std::abs(Dot(y, z)) > tolerance || Dot(Cross(x, y), z) <= T(0))
123 {
124 return std::nullopt;
125 }
126
127 // Shoemake's matrix-to-quaternion branch selection, transposed
128 // for Move's row-vector matrix convention; cross-checked against
129 // DirectXMath XMQuaternionRotationMatrix (MIT).
130 const T m00 = x.X();
131 const T m01 = x.Y();
132 const T m02 = x.Z();
133 const T m10 = y.X();
134 const T m11 = y.Y();
135 const T m12 = y.Z();
136 const T m20 = z.X();
137 const T m21 = z.Y();
138 const T m22 = z.Z();
140 const T trace = m00 + m11 + m22;
141 if (trace > T(0))
142 {
143 const T scale = std::sqrt(trace + T(1)) * T(2);
144 quaternion = Quat<T>((m12 - m21) / scale, (m20 - m02) / scale,
145 (m01 - m10) / scale, scale * T(0.25));
146 }
147 else if (m00 > m11 && m00 > m22)
148 {
149 const T scale = std::sqrt(T(1) + m00 - m11 - m22) * T(2);
150 quaternion = Quat<T>(scale * T(0.25), (m01 + m10) / scale,
151 (m02 + m20) / scale, (m12 - m21) / scale);
152 }
153 else if (m11 > m22)
154 {
155 const T scale = std::sqrt(T(1) + m11 - m00 - m22) * T(2);
156 quaternion = Quat<T>((m01 + m10) / scale, scale * T(0.25),
157 (m12 + m21) / scale, (m20 - m02) / scale);
158 }
159 else
160 {
161 const T scale = std::sqrt(T(1) + m22 - m00 - m11) * T(2);
162 quaternion = Quat<T>((m02 + m20) / scale, (m12 + m21) / scale,
163 scale * T(0.25), (m01 - m10) / scale);
164 }
165 return TryFrom(quaternion);
166 }
167
168 [[nodiscard]] static std::optional<Rotation3> TryLookTowards(
169 const Direction3<T>& forward,
170 const Direction3<T>& approximateUp) noexcept
171 {
172 // Standard Gram-Schmidt camera/object basis, cross-checked against
173 // DirectXMath XMMatrixLookToLH (MIT). +Z is Move's local forward.
174 const auto right = Direction3<T>::TryFrom(
175 Cross(approximateUp.Vector(), forward.Vector()));
176 if (!right)
177 {
178 return std::nullopt;
179 }
180 const auto up = Direction3<T>::TryFrom(
181 Cross(forward.Vector(), right->Vector()));
183 : std::nullopt;
184 }
185
187 {
188 return Value_;
189 }
190
192 {
193 return Rotation3(
194 Quat<T>(-Value_.X(), -Value_.Y(), -Value_.Z(), Value_.W()),
195 UncheckedTag{});
196 }
197
198 [[nodiscard]] Rotation3 operator*(const Rotation3& right) const noexcept
199 {
200 return *TryFrom(Value_ * right.Value_);
201 }
202
203 [[nodiscard]] friend constexpr bool operator==(
204 const Rotation3& left, const Rotation3& right) noexcept
205 {
206 return left.Value_ == right.Value_;
207 }
208
209 private:
210 struct UncheckedTag
211 {
212 };
213
214 constexpr explicit Rotation3(const Quat<T>& value,
215 UncheckedTag) noexcept :
216 Value_(value)
217 {
218 }
219
220 Quat<T> Value_{};
221 };
222
225
226 template <typename T>
228 const Rotation3<T>& left,
229 const Rotation3<T>& right,
231 {
232 const Quat<T>& leftValue = left.Quaternion();
233 const Quat<T>& rightValue = right.Quaternion();
236 }
237
238 template <typename T>
240 const Rotation3<T>& end,
241 T amount) noexcept
242 {
243 const Quat<T>& startValue = start.Quaternion();
244 Quat<T> endValue = end.Quaternion();
245 if (Dot(startValue, endValue) < T(0))
246 {
248 }
249 return *Rotation3<T>::TryFrom(startValue * (T(1) - amount) +
250 endValue * amount);
251 }
252
253 template <typename T>
255 const Rotation3<T>& end,
256 T amount) noexcept
257 {
258 return NlerpUnclamped(start, end, std::clamp(amount, T(0), T(1)));
259 }
260
261 template <typename T>
263 const Rotation3<T>& end,
264 T amount) noexcept
265 {
266 const Quat<T>& startValue = start.Quaternion();
267 Quat<T> endValue = end.Quaternion();
269 if (cosine < T(0))
270 {
271 cosine = -cosine;
273 }
274 cosine = std::clamp(cosine, T(-1), T(1));
275 if (cosine > static_cast<T>(0.9995))
276 {
277 return NlerpUnclamped(start, end, amount);
278 }
279
280 // Shoemake, "Animating Rotation with Quaternion Curves" (SIGGRAPH
281 // 1985), shortest-arc spherical interpolation.
282 const T angle = std::acos(cosine);
283 const T inverseSine = T(1) / std::sin(angle);
284 const T startWeight = std::sin((T(1) - amount) * angle) * inverseSine;
285 const T endWeight = std::sin(amount * angle) * inverseSine;
288 }
289
290 template <typename T>
292 const Rotation3<T>& end,
293 T amount) noexcept
294 {
295 return SlerpUnclamped(start, end, std::clamp(amount, T(0), T(1)));
296 }
297
298 template <typename T>
299 requires std::is_floating_point_v<T>
301 const Vec3<T>& vector) noexcept
302 {
303 const Quat<T>& quaternion = rotation.Quaternion();
304 const Vec3<T> imaginary(quaternion.X(), quaternion.Y(), quaternion.Z());
305 const Vec3<T> twiceCross = Cross(imaginary, vector) * T(2);
306 return vector + twiceCross * quaternion.W() +
308 }
309
310 template <typename T>
311 requires std::is_floating_point_v<T>
313 const Rotation3<T>& rotation, const Direction3<T>& direction) noexcept
314 {
315 return Direction3<T>(Rotate(rotation, direction.Vector()),
316 typename Direction3<T>::UncheckedTag{});
317 }
318} // namespace mv::math
319
320static_assert(sizeof(mv::math::Rotation3f) == sizeof(mv::math::Quatf));
321static_assert(alignof(mv::math::Rotation3f) == alignof(mv::math::Quatf));
322static_assert(std::is_trivially_copyable_v<mv::math::Rotation3f>);
323static_assert(std::is_standard_layout_v<mv::math::Rotation3f>);
324static_assert(sizeof(mv::math::Rotation3d) == sizeof(mv::math::Quatd));
325static_assert(alignof(mv::math::Rotation3d) == alignof(mv::math::Quatd));
326static_assert(std::is_trivially_copyable_v<mv::math::Rotation3d>);
327static_assert(std::is_standard_layout_v<mv::math::Rotation3d>);
static std::optional< Direction3 > TryFrom(const Vec3< T > &value) noexcept
constexpr T Value() const noexcept
Definition Angles.hpp:18
constexpr Rotation3() noexcept=default
constexpr Rotation3 Inverse() const noexcept
constexpr const Quat< T > & Quaternion() const noexcept
static std::optional< Rotation3 > TryFrom(const Quat< T > &value) noexcept
Definition Rotation3.hpp:53
static std::optional< Rotation3 > TryFromOrthonormalAxes(const Direction3< T > &axisX, const Direction3< T > &axisY, const Direction3< T > &axisZ, T orthogonalityTolerance=static_cast< T >(1.0e-5)) noexcept
static Rotation3 FromAxisAngle(const Direction3< T > &axis, Radians< T > angle) noexcept
Definition Rotation3.hpp:64
static std::optional< Rotation3 > TryLookTowards(const Direction3< T > &forward, const Direction3< T > &approximateUp) noexcept
static constexpr Rotation3 Identity() noexcept
Definition Rotation3.hpp:48
friend constexpr bool operator==(const Rotation3 &left, const Rotation3 &right) noexcept
Rotation3 operator*(const Rotation3 &right) const noexcept
static Rotation3 FromEuler(const EulerAngles3< T > &angles) noexcept
Definition Rotation3.hpp:80
constexpr T Dot(const Quat< T > &left, const Quat< T > &right) noexcept
Definition Quat.hpp:102
Rotation3< float > Rotation3f
bool IsNearlyEquivalent(const Rotation3< T > &left, const Rotation3< T > &right, Tolerance< T > tolerance=Tolerance< T >::Default()) noexcept
Rotation3< T > Nlerp(const Rotation3< T > &start, const Rotation3< T > &end, T amount) noexcept
Rotation3< T > Slerp(const Rotation3< T > &start, const Rotation3< T > &end, T amount) noexcept
Vec3< T > Cross(const Vec3< T > &left, const Vec3< T > &right) noexcept
Definition Vec3.hpp:367
Rotation3< T > NlerpUnclamped(const Rotation3< T > &start, const Rotation3< T > &end, T amount) noexcept
bool IsNearlyEqual(const Quat< T > &left, const Quat< T > &right, Tolerance< T > tolerance=Tolerance< T >::Default()) noexcept
Definition Quat.hpp:176
std::optional< Quat< T > > TryNormalize(const Quat< T > &value) noexcept
Definition Quat.hpp:122
Vec3< T > Rotate(const Rotation3< T > &rotation, const Vec3< T > &vector) noexcept
Rotation3< T > SlerpUnclamped(const Rotation3< T > &start, const Rotation3< T > &end, T amount) noexcept
Rotation3< double > Rotation3d