Blender V5.0
BLI_math_quaternion.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#pragma once
6
10
14
15#include "BLI_math_matrix.hh"
16
17namespace blender::math {
18
19/* -------------------------------------------------------------------- */
22
28template<typename T>
29[[nodiscard]] inline T dot(const QuaternionBase<T> &a, const QuaternionBase<T> &b);
30
36template<typename T> [[nodiscard]] QuaternionBase<T> pow(const QuaternionBase<T> &q, const T &y);
37
43template<typename T> [[nodiscard]] inline QuaternionBase<T> conjugate(const QuaternionBase<T> &a);
44
48template<typename T>
49[[nodiscard]] inline QuaternionBase<T> canonicalize(const QuaternionBase<T> &q);
50
56template<typename T> [[nodiscard]] inline QuaternionBase<T> invert(const QuaternionBase<T> &q);
57
63template<typename T>
64[[nodiscard]] inline QuaternionBase<T> invert_normalized(const QuaternionBase<T> &q);
65
70template<typename T> [[nodiscard]] inline QuaternionBase<T> normalize(const QuaternionBase<T> &q);
71template<typename T>
73 T &out_length);
74
79template<typename T>
80[[nodiscard]] inline QuaternionBase<T> interpolate(const QuaternionBase<T> &a,
81 const QuaternionBase<T> &b,
82 T t);
83
85
86/* -------------------------------------------------------------------- */
89
93template<typename T>
94[[nodiscard]] inline VecBase<T, 3> transform_point(const QuaternionBase<T> &q,
95 const VecBase<T, 3> &v);
96
98
99/* -------------------------------------------------------------------- */
102
106template<typename T> [[nodiscard]] inline bool is_zero(const QuaternionBase<T> &q)
107{
108 return q.w == T(0) && q.x == T(0) && q.y == T(0) && q.z == T(0);
109}
110
114template<typename T>
115[[nodiscard]] inline bool is_equal(const QuaternionBase<T> &a,
116 const QuaternionBase<T> &b,
117 const T epsilon = T(0))
118{
119 return math::abs(a.w - b.w) <= epsilon && math::abs(a.y - b.y) <= epsilon &&
120 math::abs(a.x - b.x) <= epsilon && math::abs(a.z - b.z) <= epsilon;
121}
122
123template<typename T> [[nodiscard]] inline bool is_unit_scale(const QuaternionBase<T> &q)
124{
125 /* Checks are flipped so NAN doesn't assert because we're making sure the value was
126 * normalized and in the case we don't want NAN to be raising asserts since there
127 * is nothing to be done in that case. */
128 const T test_unit = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
129 return (!(math::abs(test_unit - T(1)) >= AssertUnitEpsilon<QuaternionBase<T>>::value) ||
130 !(math::abs(test_unit) >= AssertUnitEpsilon<QuaternionBase<T>>::value));
131}
132
134
135/* -------------------------------------------------------------------- */
138
139/* -------------- Conversions -------------- */
140
141template<typename T> AngleRadianBase<T> QuaternionBase<T>::twist_angle(const Axis axis) const
142{
143 /* The calculation requires a canonical quaternion. */
144 const VecBase<T, 4> input_vec(canonicalize(*this));
145
146 return T(2) * AngleRadianBase<T>(input_vec[0], input_vec.yzw()[axis.as_int()]);
147}
148
149template<typename T> QuaternionBase<T> QuaternionBase<T>::swing(const Axis axis) const
150{
151 /* The calculation requires a canonical quaternion. */
152 const QuaternionBase<T> input = canonicalize(*this);
153 /* Compute swing by multiplying the original quaternion by inverted twist. */
155
157 0.0002f /*BLI_ASSERT_UNIT_EPSILON*/);
158 return swing;
159}
160
161template<typename T> QuaternionBase<T> QuaternionBase<T>::twist(const Axis axis) const
162{
163 /* The calculation requires a canonical quaternion. */
164 const VecBase<T, 4> input_vec(canonicalize(*this));
165
167 input_vec[0], input_vec.yzw()[axis.as_int()]);
168
169 VecBase<T, 4> twist(half_angle.cos(), T(0), T(0), T(0));
170 twist[axis.as_int() + 1] = half_angle.sin();
171 return QuaternionBase<T>(twist);
172}
173
174/* -------------- Methods -------------- */
175
176template<typename T>
178{
180 const QuaternionBase<T> &input = *this;
181 T len;
182 QuaternionBase<T> reference_normalized = normalize_and_get_length(reference, len);
183 /* Skips degenerate case. */
184 if (len < 1e-4f) {
185 return input;
186 }
187 QuaternionBase<T> result = reference * invert_normalized(reference_normalized) * input;
188 return (distance_squared(VecBase<T, 4>(-result), VecBase<T, 4>(reference)) <
190 -result :
191 result;
192}
193
194/* -------------- Functions -------------- */
195
196template<typename T>
197[[nodiscard]] inline T dot(const QuaternionBase<T> &a, const QuaternionBase<T> &b)
198{
199 return a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
200}
201
202template<typename T> [[nodiscard]] QuaternionBase<T> pow(const QuaternionBase<T> &q, const T &y)
203{
205 /* Reference material:
206 * https://en.wikipedia.org/wiki/Quaternion
207 *
208 * The power of a quaternion raised to an arbitrary (real) exponent y is given by:
209 * `q^x = ||q||^y * (cos(y * angle * 0.5) + n * sin(y * angle * 0.5))`
210 * where `n` is the unit vector from the imaginary part of the quaternion and
211 * where `angle` is the angle of the rotation given by `angle = 2 * acos(q.w)`.
212 *
213 * q being a unit quaternion, ||q||^y becomes 1 and is canceled out.
214 *
215 * `y * angle * 0.5` expands to `y * 2 * acos(q.w) * 0.5` which simplifies to `y * acos(q.w)`.
216 */
217 const T half_angle = y * math::safe_acos(q.w);
218 return {math::cos(half_angle), math::sin(half_angle) * normalize(q.imaginary_part())};
219}
220
221template<typename T> [[nodiscard]] inline QuaternionBase<T> conjugate(const QuaternionBase<T> &a)
222{
223 return {a.w, -a.x, -a.y, -a.z};
224}
225
226template<typename T>
228{
229 return (q.w < T(0)) ? -q : q;
230}
231
232template<typename T> [[nodiscard]] inline QuaternionBase<T> invert(const QuaternionBase<T> &q)
233{
234 const T length_squared = dot(q, q);
235 if (length_squared == T(0)) {
237 }
238 return conjugate(q) * (T(1) / length_squared);
239}
240
241template<typename T>
243{
245 return conjugate(q);
246}
247
248template<typename T>
250 T &out_length)
251{
252 out_length = math::sqrt(dot(q, q));
253 return (out_length != T(0)) ? (q * (T(1) / out_length)) : QuaternionBase<T>::identity();
254}
255
256template<typename T> [[nodiscard]] inline QuaternionBase<T> normalize(const QuaternionBase<T> &q)
257{
258 T len;
259 return normalize_and_get_length(q, len);
260}
261
270template<typename T>
271[[nodiscard]] inline VecBase<T, 2> interpolate_dot_slerp(const T t, const T cosom)
272{
273 const T eps = T(1e-4);
274
275 BLI_assert(IN_RANGE_INCL(cosom, T(-1.0001), T(1.0001)));
276
278 T abs_cosom = math::abs(cosom);
279 /* Within [-1..1] range, avoid aligned axis. */
280 if (LIKELY(abs_cosom < (T(1) - eps))) {
281 const T omega = math::acos(abs_cosom);
282 const T sinom = math::sin(omega);
283
284 w[0] = math::sin((T(1) - t) * omega) / sinom;
285 w[1] = math::sin(t * omega) / sinom;
286 }
287 else {
288 /* Fall back to lerp */
289 w[0] = T(1) - t;
290 w[1] = t;
291 }
292
293 /* Rotate around shortest angle. */
294 if (cosom < T(0)) {
295 w[0] = -w[0];
296 }
297 return w;
298}
299
300template<typename T>
301[[nodiscard]] inline QuaternionBase<T> interpolate(const QuaternionBase<T> &a,
302 const QuaternionBase<T> &b,
303 T t)
304{
305 using Vec4T = VecBase<T, 4>;
309 return QuaternionBase<T>(w[0] * Vec4T(a) + w[1] * Vec4T(b));
310}
311
312template<typename T>
313[[nodiscard]] inline VecBase<T, 3> transform_point(const QuaternionBase<T> &q,
314 const VecBase<T, 3> &v)
315{
316#if 0 /* Reference. */
318 QuaternionBase<T> R = q * V * conjugate(q);
319 return {R.x, R.y, R.z};
320#else
321 /* `S = q * V`. */
323 S.w = /* q.w * 0.0 */ -q.x * v.x - q.y * v.y - q.z * v.z;
324 S.x = q.w * v.x /* + q.x * 0.0 */ + q.y * v.z - q.z * v.y;
325 S.y = q.w * v.y /* + q.y * 0.0 */ + q.z * v.x - q.x * v.z;
326 S.z = q.w * v.z /* + q.z * 0.0 */ + q.x * v.y - q.y * v.x;
327 /* `R = S * conjugate(q)`. */
329 /* `R.w = S.w * q.w + S.x * q.x + S.y * q.y + S.z * q.z = 0.0`. */
330 R.x = S.w * -q.x + S.x * q.w - S.y * q.z + S.z * q.y;
331 R.y = S.w * -q.y + S.y * q.w - S.z * q.x + S.x * q.z;
332 R.z = S.w * -q.z + S.z * q.w - S.x * q.y + S.y * q.x;
333 return R;
334#endif
335}
336
338
339/* -------------------------------------------------------------------- */
342
343/* -------------- Constructors -------------- */
344
345template<typename T>
352
353template<typename T>
355 const QuaternionBase<T> &dual,
356 const MatBase<T, 4, 4> &scale_mat)
357 : quat(non_dual), trans(dual), scale(scale_mat), scale_weight(1), quat_weight(1)
358{
359 BLI_assert(is_unit_scale(non_dual));
360}
361
362/* -------------- Operators -------------- */
363
364template<typename T>
366{
367 DualQuaternionBase<T> &a = *this;
368 /* Sum rotation and translation. */
369
370 /* Make sure we interpolate quaternions in the right direction. */
371 if (dot(a.quat, b.quat) < 0) {
372 a.quat.w -= b.quat.w;
373 a.quat.x -= b.quat.x;
374 a.quat.y -= b.quat.y;
375 a.quat.z -= b.quat.z;
376
377 a.trans.w -= b.trans.w;
378 a.trans.x -= b.trans.x;
379 a.trans.y -= b.trans.y;
380 a.trans.z -= b.trans.z;
381 }
382 else {
383 a.quat.w += b.quat.w;
384 a.quat.x += b.quat.x;
385 a.quat.y += b.quat.y;
386 a.quat.z += b.quat.z;
387
388 a.trans.w += b.trans.w;
389 a.trans.x += b.trans.x;
390 a.trans.y += b.trans.y;
391 a.trans.z += b.trans.z;
392 }
393
394 a.quat_weight += b.quat_weight;
395
396 if (b.scale_weight > T(0)) {
397 if (a.scale_weight > T(0)) {
398 /* Weighted sum of scale matrices (sum of components). */
399 a.scale += b.scale;
400 a.scale_weight += b.scale_weight;
401 }
402 else {
403 /* No existing scale. Replace. */
404 a.scale = b.scale;
405 a.scale_weight = b.scale_weight;
406 }
407 }
408 return *this;
409}
410
412{
413 BLI_assert(t >= 0);
414 DualQuaternionBase<T> &q = *this;
415
416 q.quat.w *= t;
417 q.quat.x *= t;
418 q.quat.y *= t;
419 q.quat.z *= t;
420
421 q.trans.w *= t;
422 q.trans.x *= t;
423 q.trans.y *= t;
424 q.trans.z *= t;
425
426 q.quat_weight *= t;
427
428 if (q.scale_weight > T(0)) {
429 q.scale *= t;
430 q.scale_weight *= t;
431 }
432 return *this;
433}
434
435/* -------------- Functions -------------- */
436
444template<typename T>
446{
447 const T norm_weighted = math::sqrt(dot(dual_quat.quat, dual_quat.quat));
448 /* NOTE(fclem): Should this be an epsilon? */
449 if (norm_weighted == T(0)) {
450 /* The dual-quaternion was zero initialized or is degenerate. Return identity. */
452 }
453
454 const T inv_norm_weighted = T(1) / norm_weighted;
455
456 DualQuaternionBase<T> dq = dual_quat;
457 dq.quat = dq.quat * inv_norm_weighted;
458 dq.trans = dq.trans * inv_norm_weighted;
459
460 /* Handle scale if needed. */
461 if (dq.scale_weight > T(0)) {
462 /* Compensate for any dual quaternions added without scale.
463 * This is an optimization so that we can skip the scale part when not needed. */
464 const float missing_uniform_scale = dq.quat_weight - dq.scale_weight;
465
466 if (missing_uniform_scale > T(0)) {
467 dq.scale[0][0] += missing_uniform_scale;
468 dq.scale[1][1] += missing_uniform_scale;
469 dq.scale[2][2] += missing_uniform_scale;
470 dq.scale[3][3] += missing_uniform_scale;
471 }
472 /* Per component scalar product. */
473 dq.scale *= T(1) / dq.quat_weight;
474 dq.scale_weight = T(1);
475 }
476 dq.quat_weight = T(1);
477 return dq;
478}
479
485template<typename T>
487 const VecBase<T, 3> &point,
488 MatBase<T, 3, 3> *r_crazy_space_mat = nullptr)
489{
498 /* Follow the paper notation. */
499 const T &w0 = dq.quat.w, &x0 = dq.quat.x, &y0 = dq.quat.y, &z0 = dq.quat.z;
500 const T &we = dq.trans.w, &xe = dq.trans.x, &ye = dq.trans.y, &ze = dq.trans.z;
501 /* Part 3.4 - The Final Algorithm. */
503 t[0] = T(2) * (-we * x0 + xe * w0 - ye * z0 + ze * y0);
504 t[1] = T(2) * (-we * y0 + xe * z0 + ye * w0 - ze * x0);
505 t[2] = T(2) * (-we * z0 - xe * y0 + ye * x0 + ze * w0);
506 /* Isolate rotation matrix to easily output crazy-space mat. */
508 M[0][0] = (w0 * w0) + (x0 * x0) - (y0 * y0) - (z0 * z0); /* Same as `1 - 2y0^2 - 2z0^2`. */
509 M[0][1] = T(2) * ((x0 * y0) + (w0 * z0));
510 M[0][2] = T(2) * ((x0 * z0) - (w0 * y0));
511
512 M[1][0] = T(2) * ((x0 * y0) - (w0 * z0));
513 M[1][1] = (w0 * w0) + (y0 * y0) - (x0 * x0) - (z0 * z0); /* Same as `1 - 2x0^2 - 2z0^2`. */
514 M[1][2] = T(2) * ((y0 * z0) + (w0 * x0));
515
516 M[2][1] = T(2) * ((y0 * z0) - (w0 * x0));
517 M[2][2] = (w0 * w0) + (z0 * z0) - (x0 * x0) - (y0 * y0); /* Same as `1 - 2x0^2 - 2y0^2`. */
518 M[2][0] = T(2) * ((x0 * z0) + (w0 * y0));
519
520 VecBase<T, 3> result = point;
521 /* Apply scaling. */
522 if (dq.scale_weight != T(0)) {
523 /* NOTE(fclem): This is weird that this is also adding translation even if it is marked as
524 * scale matrix. Follows the old C implementation for now... */
526 }
527 /* Apply rotation and translation. */
529 /* Compute crazy-space correction matrix. */
530 if (r_crazy_space_mat != nullptr) {
531 if (dq.scale_weight) {
532 *r_crazy_space_mat = M * dq.scale.template view<3, 3>();
533 }
534 else {
535 *r_crazy_space_mat = M;
536 }
537 }
538 return result;
539}
540
547template<typename T>
549 const MatBase<T, 4, 4> &basemat)
550{
563 using Mat4T = MatBase<T, 4, 4>;
564 using Vec3T = VecBase<T, 3>;
565
566 /* Split scaling and rotation.
567 * There is probably a faster way to do this. It is currently done like this to correctly get
568 * negative scaling. */
569 Mat4T baseRS = mat * basemat;
570
571 Mat4T R, scale;
572 const bool has_scale = !is_orthonormal(mat) || is_negative(mat) ||
573 length_squared(to_scale(baseRS) - T(1)) > square(1e-4f);
574 if (has_scale) {
575 /* Extract Rotation and Scale. */
576 const Mat4T baseinv = invert(basemat);
577
578 /* Extra orthogonalize, to avoid flipping with stretched bones. */
580
581 Mat4T baseR = from_rotation<Mat4T>(basequat);
582 baseR.location() = baseRS.location();
583
584 R = baseR * baseinv;
585
586 const Mat4T S = invert(baseR) * baseRS;
587 /* Set scaling part. */
588 scale = basemat * S * baseinv;
589 }
590 else {
591 /* Input matrix does not contain scaling. */
592 R = mat;
593 }
594
595 /* Non-dual part. */
597
598 /* Dual part. */
599 const Vec3T &t = R.location().xyz();
601 d.w = T(-0.5) * (+t.x * q.x + t.y * q.y + t.z * q.z);
602 d.x = T(+0.5) * (+t.x * q.w + t.y * q.z - t.z * q.y);
603 d.y = T(+0.5) * (-t.x * q.z + t.y * q.w + t.z * q.x);
604 d.z = T(+0.5) * (+t.x * q.y - t.y * q.x + t.z * q.w);
605
606 if (has_scale) {
607 return DualQuaternionBase<T>(q, d, scale);
608 }
609
610 return DualQuaternionBase<T>(q, d);
611}
612
614
615} // namespace blender::math
616
617namespace blender::math {
618
619/* -------------------------------------------------------------------- */
622
623template<typename T, typename AngleT = AngleRadian>
625{
627
628 VecBase<T, 3> axis = VecBase<T, 3>(quat.x, quat.y, quat.z);
629 T cos_half_angle = quat.w;
630 T sin_half_angle = math::length(axis);
631 /* Prevent division by zero for axis conversion. */
632 if (sin_half_angle < T(0.0005)) {
633 using AngleAxisT = typename AxisAngleBase<T, AngleT>::vec3_type;
634 const AngleAxisT identity_axis = AxisAngleBase<T, AngleT>::identity().axis();
635 BLI_assert(abs(cos_half_angle) > 0.0005);
636 AxisAngleBase<T, AngleT> identity(identity_axis * sign(cos_half_angle), AngleT(0));
637 return identity;
638 }
639 /* Normalize the axis. */
640 axis /= sin_half_angle;
641
642 /* Leverage AngleT implementation of double angle. */
643 AngleT angle = AngleT(cos_half_angle, sin_half_angle) * 2;
644
645 return AxisAngleBase<T, AngleT>(axis, angle);
646}
647
649
650/* -------------------------------------------------------------------- */
653
654template<typename T> EulerXYZBase<T> to_euler(const QuaternionBase<T> &quat)
655{
656 using Mat3T = MatBase<T, 3, 3>;
658 Mat3T unit_mat = from_rotation<Mat3T>(quat);
659 return to_euler<T>(unit_mat);
660}
661
662template<typename T> Euler3Base<T> to_euler(const QuaternionBase<T> &quat, EulerOrder order)
663{
664 using Mat3T = MatBase<T, 3, 3>;
666 Mat3T unit_mat = from_rotation<Mat3T>(quat);
667 return to_euler<T>(unit_mat, order);
668}
669
671
672/* -------------------------------------------------------------------- */
675
676/* Prototype needed to avoid interdependencies of headers. */
677template<typename T, typename AngleT>
678QuaternionBase<T> to_quaternion(const AxisAngleBase<T, AngleT> &axis_angle);
679
681{
682 using AxisAngleT = AxisAngleBase<T, AngleRadianBase<T>>;
683 /* Obtain axis/angle representation. */
684 T angle;
686 if (LIKELY(angle != T(0))) {
687 return to_quaternion(AxisAngleT(axis, AngleRadianBase<T>(angle).wrapped()));
688 }
690}
691
692template<typename T> VecBase<T, 3> QuaternionBase<T>::expmap() const
693{
694 using AxisAngleT = AxisAngleBase<T, AngleRadianBase<T>>;
696 const AxisAngleT axis_angle = to_axis_angle(*this);
697 return axis_angle.axis() * axis_angle.angle().radian();
698}
699
701
702} // namespace blender::math
#define BLI_assert(a)
Definition BLI_assert.h:46
#define UNPACK3(a)
#define IN_RANGE_INCL(a, b, c)
#define LIKELY(x)
static AppView * view
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
constexpr int as_int() const
static constexpr Value Y
#define input
#define pow
VecBase< float, D > normalize(VecOp< float, D >) RET
constexpr T sign(T) RET
#define abs
#define M
#define T
#define R
QuaternionBase< T > conjugate(const QuaternionBase< T > &a)
T length_squared(const VecBase< T, Size > &a)
T cos(const AngleRadianBase< T > &a)
bool is_normalized(const DualQuaternionBase< T > &dq)
T acos(const T &a)
T sqrt(const T &a)
QuaternionBase< T > to_quaternion(const AxisAngleBase< T, AngleT > &axis_angle)
bool is_negative(const MatBase< T, 3, 3 > &mat)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
bool is_orthonormal(const MatT &mat)
T length(const VecBase< T, Size > &a)
QuaternionBase< T > normalize_and_get_length(const QuaternionBase< T > &q, T &out_length)
bool is_unit_scale(const MatBase< T, NumCol, NumRow > &m)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
bool is_zero(const T &a)
CartesianBasis invert(const CartesianBasis &basis)
T interpolate(const T &a, const T &b, const FactorT &t)
T safe_acos(const T &a)
QuaternionBase< T > invert_normalized(const QuaternionBase< T > &q)
AxisAngleBase< T, AngleT > to_axis_angle(const EulerXYZBase< T > &euler)
VecBase< T, 3 > to_scale(const MatBase< T, NumCol, NumRow > &mat)
QuaternionBase< T > canonicalize(const QuaternionBase< T > &q)
T square(const T &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
bool is_equal(const MatBase< T, NumCol, NumRow > &a, const MatBase< T, NumCol, NumRow > &b, const T epsilon=T(0))
T sin(const AngleRadianBase< T > &a)
MatT orthogonalize(const MatT &mat, const Axis axis)
T abs(const T &a)
VecBase< T, 2 > interpolate_dot_slerp(const T t, const T cosom)
MatT from_rotation(const RotationT &rotation)
DualQuaternionBase< T > to_dual_quaternion(const MatBase< T, 4, 4 > &mat, const MatBase< T, 4, 4 > &basemat)
Euler3Base< T > to_euler(const AxisAngleBase< T, AngleT > &axis_angle, EulerOrder order)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
const btScalar eps
Definition poly34.cpp:11
VecBase< T, 3 > yzw() const
static AngleCartesianBase from_point(const T &x, const T &y)
DualQuaternionBase & operator*=(const T &t) &
DualQuaternionBase & operator+=(const DualQuaternionBase &b) &
const VecBase< T, 3 > & imaginary_part() const
QuaternionBase swing(const Axis axis) const
AngleRadianBase< T > twist_angle(const Axis axis) const
QuaternionBase twist(const Axis axis) const
QuaternionBase wrapped_around(const QuaternionBase &reference) const
static QuaternionBase expmap(const VecBase< T, 3 > &expmap)
uint len
CCL_NAMESPACE_BEGIN struct Window V