JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
MinimalQuat.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2024 Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
22#include "JPLSpatial/Core.h"
23
28
29#include <cmath>
30#include <ostream>
31
32namespace JPL
33{
34 //======================================================================
36 template<CVec3 Vec3>
37 struct Quat
38 {
40 Vec3 V; // v = {x,y,z}
42
44 inline bool operator == (const Quat& rhs) const noexcept { return V == rhs.V && W == rhs.W; }
45 inline bool operator != (const Quat& rhs) const noexcept { return V != rhs.V || W != rhs.W; }
46
47 [[nodiscard]] JPL_INLINE static Quat Identity() noexcept { return Quat({ 0, 0, 0, }, 1); }
48
50
52 {
53 const Float n2Inv = 1.0f / (DotProduct(V, V) + W * W);
54 return Quat{ -V * n2Inv, W * n2Inv };
55 }
56
59 [[nodiscard]] static JPL_INLINE Quat Rotation(const Vec3& axis, Float angleRad) noexcept
60 {
61 const Float halfAngle = Float(0.5) * angleRad;
62 const auto [s, c] = Math::SinCos(halfAngle);
63 return Quat(axis * s, c);
64 }
65
68 [[nodiscard]] inline static Quat FromTo(const Vec3& from, const Vec3& to) noexcept
69 {
70 const Float dot = DotProduct(from, to);
71
72 if (dot > Float(0.999999)) // directions already equal
73 return Identity(); // no rotation
74
75 if (dot < Float(-0.999999)) // opposite directions
77
78 // General case
79 return Quat(CrossProduct(from, to), Float(1) + dot).Normalized();
80 }
81
83 [[nodiscard]] inline static Quat FromBasis(const Basis<Vec3>& basis) noexcept
84 {
85 // axes-as-columns rotation matrix:
86 const Float r00 = GetX(basis.X), r01 = GetX(basis.Y), r02 = GetX(basis.Z);
87 const Float r10 = GetY(basis.X), r11 = GetY(basis.Y), r12 = GetY(basis.Z);
88 const Float r20 = GetZ(basis.X), r21 = GetZ(basis.Y), r22 = GetZ(basis.Z);
89
90 Float tr = r00 + r11 + r22;
91 Float qw, qx, qy, qz;
92
93 if (tr > 0.0f)
94 {
95 const Float s = Math::Sqrt(tr + Float(1.0)) * Float(2.0);
96 const Float invS = Float(1.0) / s;
97 qw = Float(0.25) * s;
98 qx = (r21 - r12) * invS;
99 qy = (r02 - r20) * invS;
100 qz = (r10 - r01) * invS;
101 }
102 else if (r00 > r11 && r00 > r22)
103 {
104 const Float s = Math::Sqrt(Float(1.0) + r00 - r11 - r22) * Float(2.0);
105 const Float invS = Float(1.0) / s;
106 qw = (r21 - r12) * invS;
107 qx = Float(0.25) * s;
108 qy = (r01 + r10) * invS;
109 qz = (r02 + r20) * invS;
110 }
111 else if (r11 > r22)
112 {
113 const Float s = Math::Sqrt(Float(1.0) + r11 - r00 - r22) * Float(2.0);
114 const Float invS = Float(1.0) / s;
115 qw = (r02 - r20) * invS;
116 qx = (r01 + r10) * invS;
117 qy = Float(0.25) * s;
118 qz = (r12 + r21) * invS;
119 }
120 else
121 {
122 const Float s = Math::Sqrt(Float(1.0) + r22 - r00 - r11) * Float(2.0);
123 const Float invS = Float(1.0) / s;
124 qw = (r10 - r01) * invS;
125 qx = (r02 + r20) * invS;
126 qy = (r12 + r21) * invS;
127 qz = Float(0.25) * s;
128 }
129
130 return Quat{ Vec3{ qx, qy, qz }, qw };
131 }
132
135 [[nodiscard]] static JPL_INLINE Quat FromUpAndForward(const Vec3& up, const Vec3& forward) noexcept
136 {
138 }
139
140 [[nodiscard]] static JPL_INLINE Quat LookAt(const Vec3& direction, const Vec3& up) noexcept
141 {
143 }
144
146 [[nodiscard]] JPL_INLINE friend Quat operator*(const Quat& a, const Quat& b) noexcept
147 {
148#if 1
149 return Quat{
150 .V = a.W * b.V + b.W * a.V + CrossProduct(a.V, b.V),
151 .W = a.W * b.W - DotProduct(a.V, b.V)
152 };
153#else
154 // Let the compiler figure out optimization
155 Float lx = a.V.X;
156 Float ly = a.V.Y;
157 Float lz = a.V.Z;
158 Float lw = a.W;
159
160 Float rx = b.V.X;
161 Float ry = b.V.Y;
162 Float rz = b.V.Z;
163 Float rw = b.W;
164
165 Float x = lw * rx + lx * rw + ly * rz - lz * ry;
166 Float y = lw * ry - lx * rz + ly * rw + lz * rx;
167 Float z = lw * rz + lx * ry - ly * rx + lz * rw;
168 Float w = lw * rw - lx * rx - ly * ry - lz * rz;
169
170 return Quat({ x, y, z }, w);
171#endif
172 }
173
175 [[nodiscard]] JPL_INLINE static Quat MakeSlerp(const Vec3& from, const Vec3& to, Float t) noexcept
176 {
177 const Quat qFromTo = FromTo(from, to);
178 return Slerp(Identity(), qFromTo, t); // id -> qFromTo
179 }
180
182 [[nodiscard]] JPL_INLINE Vec3 Rotate(const Vec3& vector) const noexcept
183 {
184 // q p q* (H. Hamilton trick, 18 mul + 12 add)
185 const Vec3 t = Float(2) * CrossProduct(V, vector);
186 return vector + W * t + CrossProduct(V, t);
187 }
188
189 [[nodiscard]] JPL_INLINE Float LengthSquared() const noexcept { return DotProduct(V, V) + W * W; }
190
192
194 {
195 const Float invLen = Float(1) / Length();
196 return Quat{ V * invLen, W * invLen };
197 }
198
200 {
201 return Math::Abs(LengthSquared() - Float(1.0)) <= tolerance;
202 }
203
205 [[nodiscard]] JPL_INLINE Float GetRotationAngle(const Vec3& axis) const noexcept
206 {
207 return W == Float(0.0) ? JPL_PI : Float(2.0) * std::atan(DotProduct(V, axis) / W);
208 }
209
212 {
213 const Float xx = GetX(V) * GetX(V), yy = GetY(V) * GetY(V), zz = GetZ(V) * GetZ(V);
214 const Float xy = GetX(V) * GetY(V), xz = GetX(V) * GetZ(V), yz = GetY(V) * GetZ(V);
215 const Float wx = W * GetX(V), wy = W * GetY(V), wz = W * GetZ(V);
216 return Basis<Vec3>{
217 .X = Vec3{ Float(1) - Float(2) * (yy + zz), Float(2) * (xy + wz), Float(2) * (xz - wy) },
218 .Y = Vec3{ Float(2) * (xy - wz), Float(1) - Float(2) * (xx + zz), Float(2) * (yz + wx) },
219 .Z = Vec3{ Float(2) * (xz + wy), Float(2) * (yz - wx), Float(1) - Float(2) * (xx + yy) }
220 };
221 }
222
223 };
224
225 template<CVec3 Vec3>
226 std::ostream& operator<<(std::ostream& os, const Quat<Vec3>& quat) { os << "V ={ " << GetX(quat.V) << ", " << GetY(quat.V) << ", " << GetZ(quat.V) << " }, W = " << quat.W; return os; }
227
229 template<CVec3 Vec3>
230 [[nodiscard]] static inline Quat<Vec3> Slerp(const Quat<Vec3>& a, const Quat<Vec3>& b, typename Quat<Vec3>::Float t) noexcept
231 {
232 using F = typename Quat<Vec3>::Float;
233 F dot = a.W * b.W + DotProduct(a.V, b.V);
234 Quat<Vec3> b1 = (dot < 0) ? Quat<Vec3>{-b.W, -b.V} : b; // enforce short arc
235
236 if (Math::Abs(dot) > F(0.9995))
237 {
238 // near 0 degrees -> nlerp
239 Vec3 v = Math::Lerp(a.V, b1.V, t);
240 F w = Math::Lerp(a.W, b1.W, t);
241 return Quat<Vec3>{ v, w }.Normalized();
242 }
243
244 F phi = std::acos(dot);
245 F invSinPhi = F(1.0) / std::sin(phi);
246 F wA = std::sin((1 - t) * phi) * invSinPhi;
247 F wB = std::sin(t * phi) * invSinPhi;
248 return Quat<Vec3>{
249 .V = wA * a.V + wB * b1.V,
250 .W = wA * a.W + wB * b1.W
251 };
252 }
253
254 namespace Math
255 {
256 // Mainly just some helpers for template argument deduction
257
258 template<CVec3 Vec3>
259 [[nodiscard]] static JPL_INLINE Quat<Vec3> GetDeltaQuat(const Quat<Vec3>& a, const Quat<Vec3> b) noexcept
260 {
261 return a * b.Conjugated();
262 }
263
264 template<CVec3 Vec3>
265 [[nodiscard]] static JPL_INLINE Quat<Vec3> QuatRotation(const Vec3& axis, Internal::FloatOf<Vec3> angleRad) noexcept
266 {
268 }
269
270 template<CVec3 Vec3>
271 [[nodiscard]] static JPL_INLINE Quat<Vec3> QuatFromTo(const Vec3& from, const Vec3& to) noexcept
272 {
273 return Quat<Vec3>::FromTo(from, to);
274 }
275
276 template<CVec3 Vec3>
277 [[nodiscard]] static JPL_INLINE Quat<Vec3> QuatFromBasis(const Basis<Vec3>& basis) noexcept
278 {
280 }
281
282 template<CVec3 Vec3>
283 [[nodiscard]] static JPL_INLINE Quat<Vec3> QuatFromUpAndForward(const Vec3& up, const Vec3& forward) noexcept
284 {
286 }
287
288 template<CVec3 Vec3>
289 [[nodiscard]] static JPL_INLINE Quat<Vec3> QuatLookAt(const Vec3& direction, const Vec3& up) noexcept
290 {
292 }
293
294 } // namespace Math
295} // namespace JPL
std::remove_cvref_t< decltype(GetX(std::declval< Vec3 >()))> FloatOf
Definition Vec3Math.h:36
JPL_INLINE constexpr T Lerp(const T &v0, const T &v1, T t) noexcept
Linearly interpolate v0 towards v1.
Definition Math.h:172
JPL_INLINE constexpr T Sqrt(T x) noexcept
Definition Math.h:269
JPL_INLINE constexpr auto Abs(const T &value) noexcept
Standard abs is not constexpr in C++20.
Definition Math.h:87
JPL_INLINE std::pair< T, T > SinCos(T value) noexcept
Definition Math.h:164
Vec3 GetNormalizedPerpendicular(const Vec3 &vec) noexcept
Definition Vec3Math.h:67
Definition AcousticMaterial.h:36
JPL_INLINE auto GetX(const Vec3Type &v) noexcept
Definition Vec3Traits.h:35
JPL_INLINE auto GetZ(const Vec3Type &v) noexcept
Definition Vec3Traits.h:37
JPL_INLINE auto GetY(const Vec3Type &v) noexcept
Definition Vec3Traits.h:36
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
std::ostream & operator<<(std::ostream &os, const AcousticMaterial &v)
Definition AcousticMaterial.h:104
Orthonormal basis (column-major)
Definition MinimalBasis.h:35
Vec3 X
Definition MinimalBasis.h:38
Minimal quaternion (w + xi + yj + zk)
Definition MinimalQuat.h:38
JPL_INLINE Float Length() const noexcept
Definition MinimalQuat.h:191
Float W
Definition MinimalQuat.h:41
JPL_INLINE Float GetRotationAngle(const Vec3 &axis) const noexcept
Get rotation angle around axis.
Definition MinimalQuat.h:205
JPL_INLINE Quat Conjugated() const noexcept
Definition MinimalQuat.h:49
static JPL_INLINE Quat LookAt(const Vec3 &direction, const Vec3 &up) noexcept
Definition MinimalQuat.h:140
bool operator!=(const Quat &rhs) const noexcept
Definition MinimalQuat.h:45
JPL_INLINE Quat Inversed() const noexcept
Definition MinimalQuat.h:51
bool operator==(const Quat &rhs) const noexcept
Check if two quaternions are exactly equal.
Definition MinimalQuat.h:44
JPL_INLINE friend Quat operator*(const Quat &a, const Quat &b) noexcept
Quaternion multiplication (rotation composition)
Definition MinimalQuat.h:146
static JPL_INLINE Quat FromUpAndForward(const Vec3 &up, const Vec3 &forward) noexcept
Definition MinimalQuat.h:135
JPL_INLINE Vec3 Rotate(const Vec3 &vector) const noexcept
Rotate a vector by this quaternion.
Definition MinimalQuat.h:182
static Quat FromTo(const Vec3 &from, const Vec3 &to) noexcept
Definition MinimalQuat.h:68
static Quat FromBasis(const Basis< Vec3 > &basis) noexcept
Construct quaternian from basis columns X, Y, Z.
Definition MinimalQuat.h:83
Vec3 V
Definition MinimalQuat.h:40
Basis< Vec3 > ToBasis() const noexcept
Convert to an orthonormal Basis3 (column-major)
Definition MinimalQuat.h:211
static JPL_INLINE Quat Rotation(const Vec3 &axis, Float angleRad) noexcept
Definition MinimalQuat.h:59
JPL_INLINE Float LengthSquared() const noexcept
Definition MinimalQuat.h:189
Internal::FloatOf< Vec3 > Float
Definition MinimalQuat.h:39
JPL_INLINE bool IsNormalized(Float tolerance=Float(1.0e-5)) const noexcept
Definition MinimalQuat.h:199
static JPL_INLINE Quat MakeSlerp(const Vec3 &from, const Vec3 &to, Float t) noexcept
Create quaternian to slerp direction vector based on 'from' and 'to'.
Definition MinimalQuat.h:175
JPL_INLINE Quat Normalized() const noexcept
Definition MinimalQuat.h:193
static JPL_INLINE Quat Identity() noexcept
Definition MinimalQuat.h:47