Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquaternion.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquaternion.h"
5#include <QtCore/qdatastream.h>
6#include <QtCore/qmath.h>
7#include <QtCore/qvariant.h>
8#include <QtCore/qdebug.h>
9
10#include <cmath>
11
13
14#ifndef QT_NO_QUATERNION
15
50#ifndef QT_NO_VECTOR3D
51
77#endif
78
87#ifndef QT_NO_VECTOR4D
88
101#endif
102
199float QQuaternion::length() const
200{
201 return qHypot(xp, yp, zp, wp);
202}
203
212float QQuaternion::lengthSquared() const
213{
214 return xp * xp + yp * yp + zp * zp + wp * wp;
215}
216
227QQuaternion QQuaternion::normalized() const
228{
229 const float scale = length();
230 if (qFuzzyIsNull(scale))
231 return QQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
232 return *this / scale;
233}
234
241void QQuaternion::normalize()
242{
243 const float len = length();
244 if (qFuzzyIsNull(len))
245 return;
246
247 xp /= len;
248 yp /= len;
249 zp /= len;
250 wp /= len;
251}
252
281QVector3D QQuaternion::rotatedVector(const QVector3D &vector) const
282{
283 return (*this * QQuaternion(0, vector) * conjugated()).vector();
284}
285
329#ifndef QT_NO_VECTOR3D
330
348QQuaternion QQuaternion::fromAxisAndAngle(const QVector3D &axis, float angle)
349{
350 // Algorithm from:
351 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q56
352 // We normalize the result just in case the values are close
353 // to zero, as suggested in the above FAQ.
354 float a = qDegreesToRadians(angle / 2.0f);
355 float s = std::sin(a);
356 float c = std::cos(a);
357 QVector3D ax = axis.normalized();
358 return QQuaternion(c, ax.x() * s, ax.y() * s, ax.z() * s).normalized();
359}
360
361#endif
362
371void QQuaternion::getAxisAndAngle(float *x, float *y, float *z, float *angle) const
372{
373 Q_ASSERT(x && y && z && angle);
374
375 // The quaternion representing the rotation is
376 // q = cos(A/2)+sin(A/2)*(x*i+y*j+z*k)
377
378 const float length = qHypot(xp, yp, zp);
379 if (!qFuzzyIsNull(length)) {
380 if (qFuzzyCompare(length, 1.0f)) {
381 *x = xp;
382 *y = yp;
383 *z = zp;
384 } else {
385 *x = xp / length;
386 *y = yp / length;
387 *z = zp / length;
388 }
389 *angle = qRadiansToDegrees(2.0f * std::atan2(length, wp));
390 } else {
391 // angle is 0 (mod 2*pi), so any axis will fit
392 *x = *y = *z = *angle = 0.0f;
393 }
394}
395
402QQuaternion QQuaternion::fromAxisAndAngle
403 (float x, float y, float z, float angle)
404{
405 float length = qHypot(x, y, z);
406 if (!qFuzzyCompare(length, 1.0f) && !qFuzzyIsNull(length)) {
407 x /= length;
408 y /= length;
409 z /= length;
410 }
411 float a = qDegreesToRadians(angle / 2.0f);
412 float s = std::sin(a);
413 float c = std::cos(a);
414 return QQuaternion(c, x * s, y * s, z * s).normalized();
415}
416
417#ifndef QT_NO_VECTOR3D
418
442#endif // QT_NO_VECTOR3D
443
452void QQuaternion::getEulerAngles(float *pitch, float *yaw, float *roll) const
453{
454 Q_ASSERT(pitch && yaw && roll);
455
456 // Algorithm adapted from:
457 // https://ingmec.ual.es/~jlblanco/papers/jlblanco2010geometry3D_techrep.pdf
458 // "A tutorial on SE(3) transformation parameterizations and on-manifold optimization".
459
460 // We can only detect Gimbal lock when we normalize, which we can't do when
461 // length is nearly zero. Do so before multiplying coordinates, to avoid
462 // underflow.
463 const float len = length();
464 const bool rescale = !qFuzzyIsNull(len);
465 const float xps = rescale ? xp / len : xp;
466 const float yps = rescale ? yp / len : yp;
467 const float zps = rescale ? zp / len : zp;
468 const float wps = rescale ? wp / len : wp;
469
470 const float xx = xps * xps;
471 const float xy = xps * yps;
472 const float xz = xps * zps;
473 const float xw = xps * wps;
474 const float yy = yps * yps;
475 const float yz = yps * zps;
476 const float yw = yps * wps;
477 const float zz = zps * zps;
478 const float zw = zps * wps;
479
480 // For the common case, we have a hidden division by cos(pitch) to calculate
481 // yaw and roll: atan2(a / cos(pitch), b / cos(pitch)) = atan2(a, b). This equation
482 // wouldn't work if cos(pitch) is close to zero (i.e. abs(sin(pitch)) =~ 1.0).
483 // This threshold is copied from qFuzzyIsNull() to avoid the hidden division by zero.
484 constexpr float epsilon = 0.00001f;
485
486 const float sinp = -2.0f * (yz - xw);
487 if (std::abs(sinp) < 1.0f - epsilon) {
488 *pitch = std::asin(sinp);
489 *yaw = std::atan2(2.0f * (xz + yw), 1.0f - 2.0f * (xx + yy));
490 *roll = std::atan2(2.0f * (xy + zw), 1.0f - 2.0f * (xx + zz));
491 } else {
492 // Gimbal lock case, which doesn't have a unique solution. We just use
493 // XY rotation.
494 *pitch = std::copysign(static_cast<float>(M_PI_2), sinp);
495 *yaw = 2.0f * std::atan2(yps, wps);
496 *roll = 0.0f;
497 }
498
499 *pitch = qRadiansToDegrees(*pitch);
500 *yaw = qRadiansToDegrees(*yaw);
501 *roll = qRadiansToDegrees(*roll);
502}
503
513QQuaternion QQuaternion::fromEulerAngles(float pitch, float yaw, float roll)
514{
515 // Algorithm from:
516 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q60
517
518 pitch = qDegreesToRadians(pitch);
519 yaw = qDegreesToRadians(yaw);
520 roll = qDegreesToRadians(roll);
521
522 pitch *= 0.5f;
523 yaw *= 0.5f;
524 roll *= 0.5f;
525
526 const float c1 = std::cos(yaw);
527 const float s1 = std::sin(yaw);
528 const float c2 = std::cos(roll);
529 const float s2 = std::sin(roll);
530 const float c3 = std::cos(pitch);
531 const float s3 = std::sin(pitch);
532 const float c1c2 = c1 * c2;
533 const float s1s2 = s1 * s2;
534
535 const float w = c1c2 * c3 + s1s2 * s3;
536 const float x = c1c2 * s3 + s1s2 * c3;
537 const float y = s1 * c2 * c3 - c1 * s2 * s3;
538 const float z = c1 * s2 * c3 - s1 * c2 * s3;
539
540 return QQuaternion(w, x, y, z);
541}
542
553QMatrix3x3 QQuaternion::toRotationMatrix() const
554{
555 // Algorithm from:
556 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q54
557
559
560 const float f2x = xp + xp;
561 const float f2y = yp + yp;
562 const float f2z = zp + zp;
563 const float f2xw = f2x * wp;
564 const float f2yw = f2y * wp;
565 const float f2zw = f2z * wp;
566 const float f2xx = f2x * xp;
567 const float f2xy = f2x * yp;
568 const float f2xz = f2x * zp;
569 const float f2yy = f2y * yp;
570 const float f2yz = f2y * zp;
571 const float f2zz = f2z * zp;
572
573 rot3x3(0, 0) = 1.0f - (f2yy + f2zz);
574 rot3x3(0, 1) = f2xy - f2zw;
575 rot3x3(0, 2) = f2xz + f2yw;
576 rot3x3(1, 0) = f2xy + f2zw;
577 rot3x3(1, 1) = 1.0f - (f2xx + f2zz);
578 rot3x3(1, 2) = f2yz - f2xw;
579 rot3x3(2, 0) = f2xz - f2yw;
580 rot3x3(2, 1) = f2yz + f2xw;
581 rot3x3(2, 2) = 1.0f - (f2xx + f2yy);
582
583 return rot3x3;
584}
585
596QQuaternion QQuaternion::fromRotationMatrix(const QMatrix3x3 &rot3x3)
597{
598 // Algorithm from:
599 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q55
600
601 float scalar;
602 float axis[3];
603
604 const float trace = rot3x3(0, 0) + rot3x3(1, 1) + rot3x3(2, 2);
605 if (trace > 0.00000001f) {
606 const float s = 2.0f * std::sqrt(trace + 1.0f);
607 scalar = 0.25f * s;
608 axis[0] = (rot3x3(2, 1) - rot3x3(1, 2)) / s;
609 axis[1] = (rot3x3(0, 2) - rot3x3(2, 0)) / s;
610 axis[2] = (rot3x3(1, 0) - rot3x3(0, 1)) / s;
611 } else {
612 static int s_next[3] = { 1, 2, 0 };
613 int i = 0;
614 if (rot3x3(1, 1) > rot3x3(0, 0))
615 i = 1;
616 if (rot3x3(2, 2) > rot3x3(i, i))
617 i = 2;
618 int j = s_next[i];
619 int k = s_next[j];
620
621 const float s = 2.0f * std::sqrt(rot3x3(i, i) - rot3x3(j, j) - rot3x3(k, k) + 1.0f);
622 axis[i] = 0.25f * s;
623 scalar = (rot3x3(k, j) - rot3x3(j, k)) / s;
624 axis[j] = (rot3x3(j, i) + rot3x3(i, j)) / s;
625 axis[k] = (rot3x3(k, i) + rot3x3(i, k)) / s;
626 }
627
628 return QQuaternion(scalar, axis[0], axis[1], axis[2]);
629}
630
631#ifndef QT_NO_VECTOR3D
632
640void QQuaternion::getAxes(QVector3D *xAxis, QVector3D *yAxis, QVector3D *zAxis) const
641{
642 Q_ASSERT(xAxis && yAxis && zAxis);
643
644 const QMatrix3x3 rot3x3(toRotationMatrix());
645
646 *xAxis = QVector3D(rot3x3(0, 0), rot3x3(1, 0), rot3x3(2, 0));
647 *yAxis = QVector3D(rot3x3(0, 1), rot3x3(1, 1), rot3x3(2, 1));
648 *zAxis = QVector3D(rot3x3(0, 2), rot3x3(1, 2), rot3x3(2, 2));
649}
650
660QQuaternion QQuaternion::fromAxes(const QVector3D &xAxis, const QVector3D &yAxis, const QVector3D &zAxis)
661{
663 rot3x3(0, 0) = xAxis.x();
664 rot3x3(1, 0) = xAxis.y();
665 rot3x3(2, 0) = xAxis.z();
666 rot3x3(0, 1) = yAxis.x();
667 rot3x3(1, 1) = yAxis.y();
668 rot3x3(2, 1) = yAxis.z();
669 rot3x3(0, 2) = zAxis.x();
670 rot3x3(1, 2) = zAxis.y();
671 rot3x3(2, 2) = zAxis.z();
672
673 return QQuaternion::fromRotationMatrix(rot3x3);
674}
675
686QQuaternion QQuaternion::fromDirection(const QVector3D &direction, const QVector3D &up)
687{
689 return QQuaternion();
690
691 const QVector3D zAxis(direction.normalized());
692 QVector3D xAxis(QVector3D::crossProduct(up, zAxis));
693 if (qFuzzyIsNull(xAxis.lengthSquared())) {
694 // collinear or invalid up vector; derive shortest arc to new direction
695 return QQuaternion::rotationTo(QVector3D(0.0f, 0.0f, 1.0f), zAxis);
696 }
697
698 xAxis.normalize();
699 const QVector3D yAxis(QVector3D::crossProduct(zAxis, xAxis));
700
701 return QQuaternion::fromAxes(xAxis, yAxis, zAxis);
702}
703
712QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
713{
714 // Based on Stan Melax's article in Game Programming Gems
715
716 const QVector3D v0(from.normalized());
717 const QVector3D v1(to.normalized());
718
719 float d = QVector3D::dotProduct(v0, v1) + 1.0f;
720
721 // if dest vector is close to the inverse of source vector, ANY axis of rotation is valid
722 if (qFuzzyIsNull(d)) {
723 QVector3D axis = QVector3D::crossProduct(QVector3D(1.0f, 0.0f, 0.0f), v0);
724 if (qFuzzyIsNull(axis.lengthSquared()))
725 axis = QVector3D::crossProduct(QVector3D(0.0f, 1.0f, 0.0f), v0);
726 axis.normalize();
727
728 // same as QQuaternion::fromAxisAndAngle(axis, 180.0f)
729 return QQuaternion(0.0f, axis.x(), axis.y(), axis.z());
730 }
731
732 d = std::sqrt(2.0f * d);
733 const QVector3D axis(QVector3D::crossProduct(v0, v1) / d);
734
735 return QQuaternion(d * 0.5f, axis).normalized();
736}
737
738#endif // QT_NO_VECTOR3D
739
826#ifndef QT_NO_VECTOR3D
827
836#endif
837
857QQuaternion QQuaternion::slerp
858 (const QQuaternion &q1, const QQuaternion &q2, float t)
859{
860 // Handle the easy cases first.
861 if (t <= 0.0f)
862 return q1;
863 else if (t >= 1.0f)
864 return q2;
865
866 // Determine the angle between the two quaternions.
867 QQuaternion q2b(q2);
868 float dot = QQuaternion::dotProduct(q1, q2);
869 if (dot < 0.0f) {
870 q2b = -q2b;
871 dot = -dot;
872 }
873
874 // Get the scale factors. If they are too small,
875 // then revert to simple linear interpolation.
876 float factor1 = 1.0f - t;
877 float factor2 = t;
878 if ((1.0f - dot) > 0.0000001) {
879 float angle = std::acos(dot);
880 float sinOfAngle = std::sin(angle);
881 if (sinOfAngle > 0.0000001) {
882 factor1 = std::sin((1.0f - t) * angle) / sinOfAngle;
883 factor2 = std::sin(t * angle) / sinOfAngle;
884 }
885 }
886
887 // Construct the result quaternion.
888 return q1 * factor1 + q2b * factor2;
889}
890
906QQuaternion QQuaternion::nlerp
907 (const QQuaternion &q1, const QQuaternion &q2, float t)
908{
909 // Handle the easy cases first.
910 if (t <= 0.0f)
911 return q1;
912 else if (t >= 1.0f)
913 return q2;
914
915 // Determine the angle between the two quaternions.
916 QQuaternion q2b(q2);
917 float dot = QQuaternion::dotProduct(q1, q2);
918 if (dot < 0.0f)
919 q2b = -q2b;
920
921 // Perform the linear interpolation.
922 return (q1 * (1.0f - t) + q2b * t).normalized();
923}
924
928QQuaternion::operator QVariant() const
929{
930 return QVariant::fromValue(*this);
931}
932
933#ifndef QT_NO_DEBUG_STREAM
934
936{
937 QDebugStateSaver saver(dbg);
938 dbg.nospace() << "QQuaternion(scalar:" << q.scalar()
939 << ", vector:(" << q.x() << ", "
940 << q.y() << ", " << q.z() << "))";
941 return dbg;
942}
943
944#endif
945
946#ifndef QT_NO_DATASTREAM
947
959{
960 stream << quaternion.scalar() << quaternion.x()
961 << quaternion.y() << quaternion.z();
962 return stream;
963}
964
976{
977 float scalar, x, y, z;
978 stream >> scalar;
979 stream >> x;
980 stream >> y;
981 stream >> z;
982 quaternion.setScalar(scalar);
983 quaternion.setX(x);
984 quaternion.setY(y);
985 quaternion.setZ(z);
986 return stream;
987}
988
989#endif // QT_NO_DATASTREAM
990
991#endif
992
\inmodule QtCore\reentrant
Definition qdatastream.h:46
\inmodule QtCore
\inmodule QtCore
The QQuaternion class represents a quaternion consisting of a vector and scalar.
bool qFuzzyCompare(const QQuaternion &q1, const QQuaternion &q2) noexcept
Returns true if q1 and q2 are equal, allowing for a small fuzziness factor for floating-point compari...
\inmodule QtCore
Definition qvariant.h:65
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
QVector3D normalized() const noexcept
Returns the normalized unit vector form of this vector.
Definition qvectornd.h:695
constexpr float lengthSquared() const noexcept
Returns the squared length of the vector from the origin.
Definition qvectornd.h:713
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:671
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
static constexpr float dotProduct(QVector3D v1, QVector3D v2) noexcept
Returns the dot product of v1 and v2.
Definition qvectornd.h:770
static constexpr QVector3D crossProduct(QVector3D v1, QVector3D v2) noexcept
Returns the cross-product of vectors v1 and v2, which is normal to the plane spanned by v1 and v2.
Definition qvectornd.h:775
void normalize() noexcept
Normalizes the current vector in place.
Definition qvectornd.h:702
constexpr float z() const noexcept
Returns the z coordinate of this point.
Definition qvectornd.h:672
direction
Combined button and popup list for selecting options.
constexpr Initialization Uninitialized
Definition qctf_p.h:75
EGLStreamKHR stream
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
auto qHypot(qfloat16 x, qfloat16 y)
Definition qfloat16.h:443
constexpr float qRadiansToDegrees(float radians)
Definition qmath.h:281
#define M_PI_2
Definition qmath.h:213
constexpr float qDegreesToRadians(float degrees)
Definition qmath.h:260
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat z
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat s1
GLenum GLuint GLenum GLsizei length
GLfloat angle
GLint GLfloat v0
GLint GLfloat GLfloat v1
GLint y
GLdouble s
[6]
Definition qopenglext.h:235
const GLubyte * c
GLdouble GLdouble t
Definition qopenglext.h:243
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLenum GLsizei len
GLenum GLenum GLenum GLenum GLenum scale
static qreal dot(const QPointF &a, const QPointF &b)
QDebug operator<<(QDebug dbg, const QQuaternion &q)
QDataStream & operator>>(QDataStream &stream, QQuaternion &quaternion)
static const qreal epsilon
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define s3
#define s2
#define v1
#define v0
QList< int > vector
[14]
MyCustomStruct c2