SuperDex Physics C++ API
Loading...
Searching...
No Matches
quaternion_inl.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include "quaternion.h" // For IntelliSense
20
21namespace superdex {
22
23/************************************************************************************
24 Quaternion Inlines
25*/
29
33
35 return Quaternion{Sin(a * 0.5_r), 0_r, 0_r, Cos(a * 0.5_r)};
36}
37
39 return Quaternion{0_r, Sin(a * 0.5_r), 0_r, Cos(a * 0.5_r)};
40}
41
43 return Quaternion{0_r, 0_r, Sin(a * 0.5_r), Cos(a * 0.5_r)};
44}
45
47 return Real4{data[0], data[1], data[2], data[3]};
48}
49
50inline Quaternion Quaternion::FromUnitAxisAngle(Vec4r normalizedAxis, real angleRadians) {
51 // Log a warning in debug builds if the axis is not unit length (non-fatal unlike an assert).
52#if MOCHI_ASSERT_VERBOSE_ENABLED && MOCHI_LOG_ENABLED
53 auto norm = Norm<3>(normalizedAxis);
54 if (!NearEqual(norm, 1_r, 20_r * kDefaultNearEqualEpsilon<real>))
57 "Quaternion::FromUnitAxisAngle expects a unit length vector, but got [%g, %g, %g] (magnitude %g).",
58 normalizedAxis[0],
59 normalizedAxis[1],
60 normalizedAxis[2],
61 norm);
62 }
63#endif
64 // Use double precision to avoid rounding error in the trig functions.
65 double c = Cos(static_cast<double>(angleRadians) * 0.5);
66 double s = Sin(static_cast<double>(angleRadians) * 0.5);
67 Vec4d v = Set<3>(StaticCast<Vec4d>(normalizedAxis) * s, c);
69}
70
71inline Quaternion Quaternion::FromAxisAngle(Vec4r axis, real angleRadians) {
72 return FromUnitAxisAngle(Normalize<3>(axis), angleRadians);
73}
74
75inline Quaternion Quaternion::FromAxisAngle(Real3 const& axis, real angleRadians) {
76 return FromAxisAngle(Vec4r{axis[0], axis[1], axis[2], 0_r}, angleRadians);
77}
78
80 Vec4r angle = VNorm<3>(rotVector);
81 real rangle = Get0(angle);
82 if (rangle > 1e-9_r)
84 Vec4r axis = ToSimdDirection(rotVector) / angle;
85 return FromUnitAxisAngle(axis, rangle);
86 }
87 else {
88 // q = (xyz, w)
89 // xyz = v/|v| * sin(|v|/2) ≈ v/|v| * |v|/2 = 0.5 * v
90 // w = cos(|v|/2) ≈ 1 - |v|^2/8 ≈ 1
91 return Quaternion(ToSimdPoint(0.5_r * rotVector));
92 }
93}
94
96 Vec4r vrotVector(rotVector[0], rotVector[1], rotVector[2], 0.0_r);
97 return FromRotationVector(vrotVector);
98}
99
100inline real Quaternion::GetAngleImpl(Quaternion& qPositiveNormalized, real& vectorMag) const {
101 // We compute the angle using the part of the quaternion (the vector part (x, y, z) or the scalar
102 // part w) that is smaller, as this provides higher precision.
103 qPositiveNormalized = Quaternion{Normalize<4>(this->data)};
104 // For simplicity, use always the quaternion with positive scalar part.
105 if (qPositiveNormalized.data[3] < 0_r) {
106 qPositiveNormalized.data = -qPositiveNormalized.data;
107 }
108 real w = qPositiveNormalized.data[3]; // Scalar part (always >= 0)
109 vectorMag = Norm<3>(qPositiveNormalized.data); // Norm of the vector part
110 // Note that vectorMag^2 + w^2 = 1. However, we do not compute vectorMag = sqrt(1 - w^2) to avoid
111 // catastrophic cancellation when vectorMag is small but w = 1. This can often happen with single
112 // precision for small angles.
113 return 2_r * (w < vectorMag ? ACos(w) : ASin(vectorMag));
114}
115
117 Quaternion qPositiveNormalized MOCHI_NO_INIT; // discarded
118 real vectorMag MOCHI_NO_INIT; // discarded
119 return GetAngleImpl(qPositiveNormalized, vectorMag);
120}
121
122inline void Quaternion::ToAxisAngle(Real3* outAxis, real* outAngleRad) const {
123 Quaternion qPositiveNormalized MOCHI_NO_INIT;
124 real vectorMag MOCHI_NO_INIT;
125 *outAngleRad = GetAngleImpl(qPositiveNormalized, vectorMag);
126 auto axis =
127 vectorMag > 1e-9_r ? (qPositiveNormalized.data / vectorMag) : Vec4r{1_r, 0_r, 0_r, 0_r};
128 *outAxis = Real3{axis[0], axis[1], axis[2]};
129}
130
131inline void Quaternion::ToAxisAngle(Vec4r* outAxis, real* outAngleRad) const {
132 Real3 outAxisTemp MOCHI_NO_INIT;
133 this->ToAxisAngle(&outAxisTemp, outAngleRad);
134 *outAxis = Vec4r{outAxisTemp[0], outAxisTemp[1], outAxisTemp[2], 0_r};
135}
136
138 Vec4r outAxis MOCHI_NO_INIT; // NOLINT(cppcoreguidelines-init-variables)
139 real outAngle MOCHI_NO_INIT; // NOLINT(cppcoreguidelines-init-variables)
140 this->ToAxisAngle(&outAxis, &outAngle);
141 return outAxis * outAngle;
142}
143
145 auto r = VToRotationVector();
146 return Real3{r[0], r[1], r[2]};
147}
148
150 return data == rhs.data;
151}
152
154 return data != rhs.data;
155}
156
169
173
175 // The canonical way to multiply this quaternion q by vector v is:
176 //
177 // v' = q * v * conjugate(q)
178 //
179 // where the vector v is treated as a quaternion with w=0, but that is rather expensive since
180 // quaternion multiplication is not very SIMD friendly (unless you do multiple pairs of
181 // quaternions at once). Fortunately, there is a faster way:
182 //
183 // t = 2 * cross(q.xyz, v)
184 // v' = v + q.w * t + cross(q.xyz, t)
185 //
186 // With the MSVC x64 compiler, this results in 27 total instructions vs 55 instructions for the
187 // canonical approach. A derivation of the formula can be found here:
188 // https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/
189 //
190 Vec4r c = Cross3(this->data, v);
191 Vec4r t = c + c;
192 Vec4r qw = Broadcast<3>(this->data);
193 return MulAdd(qw, t, v) + Cross3(this->data, t);
194}
195
197 auto r = *this * Vec4r{v[0], v[1], v[2], 0_r};
198 return Real3{r[0], r[1], r[2]};
199}
200
204
208
212
213} // namespace superdex
214
215/************************************************************************************
216 Reflection support for Quaternion.
217 Serializes like std::array<real, 4>.
218*/
219#if MOCHI_USE_REFLECTION
220template <>
221struct SReflectTypeTraits<superdex::Quaternion> {
222 static constexpr SReflect::CoreType coreType = SReflect::CoreType::CT_array;
223 static SReflect::ArrayTypeInfo const& GetTypeInfo() {
224 static auto const* s_typeInfo =
225 SReflect::MakeFixedArrayTypeInfo<superdex::Quaternion, superdex::real, 4>("superdex::Quaternion");
226 return *s_typeInfo;
227 }
228};
229#endif // MOCHI_USE_REFLECTION
static Quaternion RotationY(real a)
Quaternion operator*(Quaternion const &rhs) const
static Quaternion FromUnitAxisAngle(Vec4r normalizedAxis, real angleRadians)
Quaternion GetConjugate() const
Real3 ToRotationVector() const
void ToAxisAngle(Real3 *outAxis, real *outAngleRad) const
bool operator!=(Quaternion const &rhs) const
static Quaternion Identity()
static Quaternion RotationX(real a)
bool operator==(Quaternion const &rhs) const
static Quaternion RotationZ(real a)
static Quaternion FromAxisAngle(Vec4r axis, real angleRadians)
static Quaternion Zero()
Quaternion operator-() const
Vec4r VToRotationVector() const
static Quaternion FromRotationVector(Vec4r rotVector)
Quaternion operator+(Quaternion const &a) const
Real4 ToReal4() const
#define MOCHI_LOG_WARNING(...)
Definition log.h:43
#define MOCHI_UNLIKELY
#define MOCHI_FORCE_INLINE
#define MOCHI_LIKELY
#define MOCHI_NO_INIT
constexpr T ACos(T a)
Simd< T, 2 > Shuffle(Simd< T, 2 > a)
Definition simd_inl.h:270
Simd< T, N > Cross3(Simd< T, N > a, Simd< T, N > b)
Definition simd_inl.h:832
constexpr T Sin(T a)
constexpr To StaticCast(From const &a)
Definition basic_utils.h:79
constexpr T kDefaultNearEqualEpsilon
T Norm(Simd< T, N > a)
Definition simd_inl.h:854
constexpr auto MulAdd(A a, B b, C c)
V ToSimdDirection(V a)
Definition simd_inl.h:179
Simd< T, N > Set(Simd< T, N > a, T value)
Definition simd_inl.h:315
Simd< real, 4 > Vec4r
Definition simd.h:206
constexpr T Cos(T a)
V SimdZero()
Definition simd_inl.h:146
V Broadcast(typename V::Scalar a)
Definition simd_inl.h:115
Simd< T, N > Normalize(Simd< T, N > a)
Definition simd_inl.h:859
Simd< double, 4 > Vec4d
Definition simd.h:202
NdArray< real, 3 > Real3
Definition nd_array.h:106
constexpr T ASin(T a)
NdArray< real, 4 > Real4
Definition nd_array.h:107
T Get0(Simd< T, N > v)
Definition simd_inl.h:295
Simd< T, N > VNorm(Simd< T, N > a)
Definition simd_inl.h:844
bool NearEqual(TransformRT const &a, TransformRT const &b, real epsilon=kDefaultNearEqualEpsilon< real >)
Simd< T, N > Neg(Simd< T, N > a)
Definition simd_inl.h:340
V ToSimdPoint(V a)
Definition simd_inl.h:174