BELFEM 0.9.0
Berkeley Lab Finite Element Framework
Loading...
Searching...
No Matches
cl_Quaternion.hpp
Go to the documentation of this file.
1/*
2 * BELFEM -- The Berkeley Lab Finite Element Framework
3 * Copyright (c) 2026, The Regents of the University of California,
4 * through Lawrence Berkeley National Laboratory (subject to receipt of any required
5 * approvals from the U.S. Dept. of Energy). All rights reserved.
6 *
7 * Developers: Christian Messe, Gregory Giard
8 *
9 * See the top-level LICENSE file for the complete license and disclaimer.
10 */
11
12#ifndef CL_QUATERNION_HPP
13#define CL_QUATERNION_HPP
14
15#include <algorithm>
16#include <cmath>
17#include <cstddef>
18#include <initializer_list>
19
20#include "assert.hpp"
21#include "cl_Vector.hpp"
22
23namespace belfem
24{
31 template < typename T >
33 {
39 T mData[ 4 ] ;
40
41 public:
42//------------------------------------------------------------------------------
43// Constructors
44//------------------------------------------------------------------------------
45
47 mData{ T ( 0 ), T ( 0 ), T ( 0 ), T ( 0 ) }
48 {
49 }
50
51 Quaternion ( T a, T b, T c, T d ) :
52 mData{ a, b, c, d }
53 {
54 }
55
56 Quaternion ( const Vector < T > & aVector )
57 {
58 BELFEM_ASSERT( aVector.length() == 3, "Must pass a vector of length 3" );
59 mData [ 0 ] = T ( 0 );
60 mData [ 1 ] = aVector( 0 );
61 mData [ 2 ] = aVector( 1 );
62 mData [ 3 ] = aVector( 2 );
63 }
64
70 Quaternion ( const Vector < T > & aAxis, const T aAngle )
71 {
72 BELFEM_ASSERT( aAxis.length() == 3, "Axis must be a vector of length 3" );
73
74 // normalize the axis
75 T tNorm = std::sqrt( aAxis( 0 ) * aAxis( 0 ) + aAxis( 1 ) * aAxis( 1 ) + aAxis( 2 ) * aAxis( 2 ) );
76 BELFEM_ASSERT( tNorm > BELFEM_EPSILON, "Axis cannot be zero vector" );
77
78 T tHalfAngle = aAngle * T ( 0.5 );
79 T tSinHalf = std::sin( tHalfAngle ) / tNorm;
80
81 mData [ 0 ] = std::cos( tHalfAngle );
82 mData [ 1 ] = aAxis( 0 ) * tSinHalf;
83 mData [ 2 ] = aAxis( 1 ) * tSinHalf;
84 mData [ 3 ] = aAxis( 2 ) * tSinHalf;
85 }
86
87 // Copy/move construction, copy/move assignment, and destruction are all
88 // trivial (rule of zero): the only data member is a four-element value
89 // array. This is what makes Quaternion safe to copy and MPI-transfer.
90
92 {
93 return { T ( 1 ), T ( 0 ), T ( 0 ), T ( 0 ) };
94 }
95
96//------------------------------------------------------------------------------
97// Accessors
98//------------------------------------------------------------------------------
99
100 T * data()
101 {
102 return mData;
103 }
104
105 const T * data() const
106 {
107 return mData;
108 }
109
110 T & a()
111 {
112 return mData [ 0 ];
113 }
114
115 const T & a() const
116 {
117 return mData [ 0 ];
118 }
119
120 T & b()
121 {
122 return mData [ 1 ];
123 }
124
125 const T & b() const
126 {
127 return mData [ 1 ];
128 }
129
130 T & c()
131 {
132 return mData [ 2 ];
133 }
134
135 const T & c() const
136 {
137 return mData [ 2 ];
138 }
139
140 T & d()
141 {
142 return mData [ 3 ];
143 }
144
145 const T & d() const
146 {
147 return mData [ 3 ];
148 }
149
150//------------------------------------------------------------------------------
151// Iterators
152//------------------------------------------------------------------------------
153
154 T * begin()
155 {
156 return mData;
157 }
158
159 const T * begin() const
160 {
161 return mData;
162 }
163
164 T * end()
165 {
166 return mData + 4;
167 }
168
169 const T * end() const
170 {
171 return mData + 4;
172 }
173
174//------------------------------------------------------------------------------
175// Operators
176//------------------------------------------------------------------------------
177
178 Quaternion & operator= ( std::initializer_list < T > initList )
179 {
180 BELFEM_ASSERT ( initList.size() == 4, "Initializer list must be of length 4" );
181 size_t tCount = 0;
182 for ( const T & value : initList )
183 {
184 mData [ tCount++ ] = value;
185 }
186 return *this;
187 }
188
190 {
191 mData [ 0 ] += aOther.mData [ 0 ];
192 mData [ 1 ] += aOther.mData [ 1 ];
193 mData [ 2 ] += aOther.mData [ 2 ];
194 mData [ 3 ] += aOther.mData [ 3 ];
195 return *this;
196 }
197
199 {
200 mData [ 0 ] -= aOther.mData [ 0 ];
201 mData [ 1 ] -= aOther.mData [ 1 ];
202 mData [ 2 ] -= aOther.mData [ 2 ];
203 mData [ 3 ] -= aOther.mData [ 3 ];
204 return *this;
205 }
206
207 Quaternion & operator*= ( T aScalar )
208 {
209 mData [ 0 ] *= aScalar;
210 mData [ 1 ] *= aScalar;
211 mData [ 2 ] *= aScalar;
212 mData [ 3 ] *= aScalar;
213 return *this;
214 }
215
216 Quaternion & operator/= ( T aScalar )
217 {
218 BELFEM_ERROR ( std::abs ( aScalar ) > BELFEM_EPSILON, "Division by zero" );
219 mData [ 0 ] /= aScalar;
220 mData [ 1 ] /= aScalar;
221 mData [ 2 ] /= aScalar;
222 mData [ 3 ] /= aScalar;
223 return *this;
224 }
225
227 {
228 T a1 = mData [ 0 ];
229 T b1 = mData [ 1 ];
230 T c1 = mData [ 2 ];
231 T d1 = mData [ 3 ];
232
233 T a2 = aOther.mData [ 0 ];
234 T b2 = aOther.mData [ 1 ];
235 T c2 = aOther.mData [ 2 ];
236 T d2 = aOther.mData [ 3 ];
237
238 mData [ 0 ] = a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2;
239 mData [ 1 ] = a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2;
240 mData [ 2 ] = a1 * c2 - b1 * d2 + c1 * a2 + d1 * b2;
241 mData [ 3 ] = a1 * d2 + b1 * c2 - c1 * b2 + d1 * a2;
242 return *this;
243 }
244
245//------------------------------------------------------------------------------
246// Special functions
247//------------------------------------------------------------------------------
248
249 T norm() const
250 {
251 return std::sqrt ( mData [ 0 ] * mData [ 0 ] + mData [ 1 ] * mData [ 1 ] + mData [ 2 ] * mData [ 2 ] + mData [ 3 ] * mData [ 3 ] );
252 }
253
255 conj() const
256 {
257 return { mData [ 0 ], -mData [ 1 ], -mData [ 2 ], -mData [ 3 ] };
258 }
259
261 inv() const
262 {
263 T tSqNorm = mData [ 0 ] * mData [ 0 ] + mData [ 1 ] * mData [ 1 ] + mData [ 2 ] * mData [ 2 ] + mData [ 3 ] * mData [ 3 ];
264 BELFEM_ERROR ( tSqNorm > BELFEM_EPSILON, "Zero quaternion has no inverse" );
265 return conj() / tSqNorm;
266 }
267
270 {
271 T tNorm = norm();
272 BELFEM_ERROR ( tNorm > BELFEM_EPSILON, "Cannot normalize zero quaternion" );
273 *this /= tNorm;
274 return *this;
275 }
276
285 Vector < T >
286 rotate( const Vector < T > & aVector ) const
287 {
288 BELFEM_ASSERT( std::abs( this->norm() - T( 1 ) ) < T( 100 ) * BELFEM_EPSILON,
289 "rotate() requires a unit quaternion" );
290 BELFEM_ASSERT( aVector.length() == 3, "Vector must have length 3" );
291
292 // embed vector as pure quaternion (0, vx, vy, vz)
293 Quaternion < T > tV ( T ( 0 ), aVector( 0 ), aVector( 1 ), aVector( 2 ) );
294
295 // compute q * v * q* (unit quaternion, so conjugate = inverse)
296 Quaternion < T > tResult = ( *this ) * tV * this->conj();
297
298 // extract the vector part
299 Vector < T > tOut( 3 );
300 tOut( 0 ) = tResult.b();
301 tOut( 1 ) = tResult.c();
302 tOut( 2 ) = tResult.d();
303 return tOut;
304 }
305 };
306
307 // Non-member operators
308 template < typename T >
309 Quaternion < T > operator+ ( const Quaternion < T > & aLHS, const Quaternion < T > & aRHS )
310 {
311 Quaternion < T > aResult = aLHS;
312 aResult += aRHS;
313 return aResult;
314 }
315
316 template < typename T >
317 Quaternion < T > operator- ( const Quaternion < T > & aLHS, const Quaternion < T > & aRHS )
318 {
319 Quaternion < T > aResult = aLHS;
320 aResult -= aRHS;
321 return aResult;
322 }
323
324 template < typename T >
325 Quaternion < T > operator* ( const Quaternion < T > & aLHS, T aScalar )
326 {
327 Quaternion < T > aResult = aLHS;
328 aResult *= aScalar;
329 return aResult;
330 }
331
332 template < typename T >
333 Quaternion < T > operator* ( T aScalar, const Quaternion < T > & aRHS )
334 {
335 return aRHS * aScalar;
336 }
337
338 template < typename T >
339 Quaternion < T > operator/ ( const Quaternion < T > & aLHS, T aScalar )
340 {
341 Quaternion < T > aResult = aLHS;
342 aResult /= aScalar;
343 return aResult;
344 }
345
346 template < typename T >
347 Quaternion < T > operator* ( const Quaternion < T > & aLHS, const Quaternion < T > & aRHS )
348 {
349 Quaternion < T > aResult = aLHS;
350 aResult *= aRHS;
351 return aResult;
352 }
353
354 // Component-wise comparison. Note: for unit quaternions representing
355 // rotations, q and -q encode the same rotation. This operator does NOT
356 // account for that — it compares quaternions as 4-vectors.
357 template < typename T >
358 bool operator== ( const Quaternion < T > & aLHS, const Quaternion < T > & aRHS )
359 {
360 if ( std::abs ( aLHS.a() - aRHS.a() ) > BELFEM_EPSILON )
361 return false;
362 if ( std::abs ( aLHS.b() - aRHS.b() ) > BELFEM_EPSILON )
363 return false;
364 if ( std::abs ( aLHS.c() - aRHS.c() ) > BELFEM_EPSILON )
365 return false;
366 if ( std::abs ( aLHS.d() - aRHS.d() ) > BELFEM_EPSILON )
367 return false;
368 return true;
369 }
370
371 template < typename T >
372 bool operator!= ( const Quaternion < T > & aLHS, const Quaternion < T > & aRHS )
373 {
374 return ! ( aLHS == aRHS );
375 }
376
377 template < typename T >
378 T dot ( const Quaternion < T > & aLHS, const Quaternion < T > & aRHS )
379 {
380 return aLHS.a() * aRHS.a() + aLHS.b() * aRHS.b() + aLHS.c() * aRHS.c() + aLHS.d() * aRHS.d();
381 }
382
383 template < typename T >
384 Quaternion < T > cross ( const Quaternion < T > & aLHS, const Quaternion < T > & aRHS )
385 {
386 return { T ( 0 ), aLHS.c() * aRHS.d() - aLHS.d() * aRHS.c(), aLHS.d() * aRHS.b() - aLHS.b() * aRHS.d(), aLHS.b() * aRHS.c() - aLHS.c() * aRHS.b() };
387 }
388
399 template < typename T >
400 Quaternion < T > slerp ( const Quaternion < T > & aQ1, const Quaternion < T > & aQ2, T aT )
401 {
402 BELFEM_ASSERT( std::abs( aQ1.norm() - T( 1 ) ) < T( 100 ) * BELFEM_EPSILON,
403 "slerp: aQ1 must be a unit quaternion" );
404 BELFEM_ASSERT( std::abs( aQ2.norm() - T( 1 ) ) < T( 100 ) * BELFEM_EPSILON,
405 "slerp: aQ2 must be a unit quaternion" );
406
407 // compute cosine of angle between quaternions
408 T tCosTheta = dot( aQ1, aQ2 );
409
410 // if dot product is negative, negate one quaternion to take the shorter path
411 Quaternion < T > tQ2 = aQ2;
412 if ( tCosTheta < T ( 0 ) )
413 {
414 tQ2 = aQ2 * T ( -1 );
415 tCosTheta = -tCosTheta;
416 }
417
418 // if quaternions are very close, use linear interpolation to avoid division by zero
419 if ( tCosTheta > T ( 1 ) - BELFEM_EPSILON )
420 {
421 Quaternion < T > tResult = aQ1 * ( T ( 1 ) - aT ) + tQ2 * aT;
422 tResult.normalize();
423 return tResult;
424 }
425
426 // standard slerp formula
427 T tTheta = std::acos( tCosTheta );
428 T tSinTheta = std::sin( tTheta );
429
430 T tW1 = std::sin( ( T ( 1 ) - aT ) * tTheta ) / tSinTheta;
431 T tW2 = std::sin( aT * tTheta ) / tSinTheta;
432
433 return aQ1 * tW1 + tQ2 * tW2;
434 }
435}
436#endif //CL_QUATERNION_HPP
#define BELFEM_ERROR(aCheck,...)
Definition assert.hpp:264
#define BELFEM_ASSERT(aCheck,...)
Definition assert.hpp:244
Scalar-first quaternion value type for 3D rotations.
Definition cl_Quaternion.hpp:33
Quaternion()
Definition cl_Quaternion.hpp:46
Quaternion< T > conj() const
Definition cl_Quaternion.hpp:255
T * end()
Definition cl_Quaternion.hpp:164
const T & a() const
Definition cl_Quaternion.hpp:115
T & a()
Definition cl_Quaternion.hpp:110
Quaternion & operator/=(T aScalar)
Definition cl_Quaternion.hpp:216
Quaternion & operator+=(const Quaternion &aOther)
Definition cl_Quaternion.hpp:189
T * begin()
Definition cl_Quaternion.hpp:154
Quaternion(const Vector< T > &aAxis, const T aAngle)
Construct a rotation quaternion from axis and angle.
Definition cl_Quaternion.hpp:70
Quaternion< T > inv() const
Definition cl_Quaternion.hpp:261
Quaternion & operator-=(const Quaternion &aOther)
Definition cl_Quaternion.hpp:198
const T & d() const
Definition cl_Quaternion.hpp:145
const T * data() const
Definition cl_Quaternion.hpp:105
Quaternion & operator=(std::initializer_list< T > initList)
Definition cl_Quaternion.hpp:178
Quaternion< T > & normalize()
Definition cl_Quaternion.hpp:269
Vector< T > rotate(const Vector< T > &aVector) const
Rotate a 3D vector by this unit quaternion.
Definition cl_Quaternion.hpp:286
Quaternion & operator*=(T aScalar)
Definition cl_Quaternion.hpp:207
const T * end() const
Definition cl_Quaternion.hpp:169
static Quaternion identity()
Definition cl_Quaternion.hpp:91
const T & c() const
Definition cl_Quaternion.hpp:135
T * data()
Definition cl_Quaternion.hpp:100
const T & b() const
Definition cl_Quaternion.hpp:125
T & d()
Definition cl_Quaternion.hpp:140
T & c()
Definition cl_Quaternion.hpp:130
Quaternion(T a, T b, T c, T d)
Definition cl_Quaternion.hpp:51
T & b()
Definition cl_Quaternion.hpp:120
const T * begin() const
Definition cl_Quaternion.hpp:159
T norm() const
Definition cl_Quaternion.hpp:249
Quaternion(const Vector< T > &aVector)
Definition cl_Quaternion.hpp:56
size_t length() const
get the length of the vector
Definition cl_AR_Vector.hpp:257
auto dot(const Vector< T > &aA, const Vector< T > &aB) -> decltype(arma::dot(aA.vector_data(), aB.vector_data()))
Scalar product of two vectors.
Definition fn_AR_dot.hpp:24
USER GUIDES:
Definition cl_Capacitor.cpp:16
auto operator+(const Matrix< T > &aA, const Matrix< T > &aB) -> decltype(aA.matrix_data()+aB.matrix_data())
Definition op_MatrixPlus.hpp:23
std::pair< real, unit > value
Definition typedefs.hpp:74
bool operator!=(const Quaternion< T > &aLHS, const Quaternion< T > &aRHS)
Definition cl_Quaternion.hpp:372
Quaternion< T > slerp(const Quaternion< T > &aQ1, const Quaternion< T > &aQ2, T aT)
Spherical linear interpolation between two quaternions.
Definition cl_Quaternion.hpp:400
constexpr real BELFEM_EPSILON
Definition typedefs.hpp:90
auto operator/(const Vector< T > &aA, const T &aB) -> decltype(aA.vector_data()/aB)
Definition op_VectorDivide.hpp:23
auto cross(const ET &aA, const ET &aB) -> decltype(arma::cross(aA, aB))
Definition fn_AR_cross.hpp:23
auto operator*(const Matrix< T > &aA, const Matrix< T > &aB) -> decltype(aA.matrix_data() *aB.matrix_data())
Definition op_MatrixTimes.hpp:24
auto operator-(const Matrix< T > &aA, const Matrix< T > &aB) -> decltype(aA.matrix_data() - aB.matrix_data())
Definition op_MatrixMinus.hpp:23
bool operator==(const Matrix< T > &aA, const Matrix< T > &aB)
Definition op_AR_MatrixEqualEqual.hpp:23