From cc5b5232ff603e9af775f664ce645ac6129690ee Mon Sep 17 00:00:00 2001 From: BlueRose <378100977@qq.com> Date: Thu, 9 Nov 2023 13:21:25 +0800 Subject: [PATCH] vault backup: 2023-11-09 13:21:25 --- 03-UnrealEngine/Math/四元数学习笔记.md | 215 ++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 4 deletions(-) diff --git a/03-UnrealEngine/Math/四元数学习笔记.md b/03-UnrealEngine/Math/四元数学习笔记.md index 66f7a6f..ed8f11d 100644 --- a/03-UnrealEngine/Math/四元数学习笔记.md +++ b/03-UnrealEngine/Math/四元数学习笔记.md @@ -7,9 +7,12 @@ rating: ⭐ --- # 前言 -推荐学习视频: -- [四元数的可视化](https://www.bilibili.com/video/BV1SW411y7W1/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290) -- [四元数和三维转动,可互动的探索式视频](https://www.bilibili.com/video/BV14Y4y1z7xW/?spm_id_from=333.788.recommend_more_video.1&vd_source=d47c0bb42f9c72fd7d74562185cee290) +- 学习视频: + - [四元数的可视化](https://www.bilibili.com/video/BV1SW411y7W1/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=d47c0bb42f9c72fd7d74562185cee290) + - [四元数和三维转动,可互动的探索式视频](https://www.bilibili.com/video/BV14Y4y1z7xW/?spm_id_from=333.788.recommend_more_video.1&vd_source=d47c0bb42f9c72fd7d74562185cee290) +- 文章: + - https://zhuanlan.zhihu.com/p/104288667 + - https://zhuanlan.zhihu.com/p/442146306 另外作者还建立了一个四元数可视化网站: https://eater.net/quaternions ,点击里面的教学视频之后点击正方上的按钮就可以停止播放视频,并且可以手动操作四元数。 @@ -94,7 +97,211 @@ inline QQuaternion QQuaternion::inverted() const ## 四元数复合旋转 $$q_{next}=q_2q_1$$ $$v'=q_2q_1vq_1^*q_2^*$$ -`q*`为反函数。 +可以通过推导以及模长等效计算的方式得出。以下假设r为单位四元数: +$$p'=p{\parallel}+rp{\perp}= 1 \cdot p{\parallel} + rp{\perp}=qq^{-1} p{\parallel}+qqp{\perp}=qp{\parallel}q^*+qp{\perp}q^*=q(p{\parallel}+p{\perp})q^*$$ + +因为$$p=p{\parallel}+p{\perp}$$ +所以$$p'=qpq^*$$ +## 共轭四元数与求反 +对于模长为1的单位四元数,我们可以写成: +$$q^*=q^{-1}$$ +`q*`为共轭四元数。共轭四元数可以通过对四元数的向量部分求反来计算: +$$q=[s,v]$$ +$$q^*=[s,-v]$$ +## 四元数与欧拉数转换 +>**为什么实际旋转角度是四元数里面的角度的两倍?有什么数学上的原因吗?** +- https://www.zhihu.com/question/41485992/answer/91194777 + - 这个回答比较直观:https://www.zhihu.com/question/41485992/answer/776988632 + +> 主要原因是,普通三维空间,若矢量是r,旋转变换矩阵是S,那么矢量变成 Sr 。即矢量的变换,是用变换矩阵直接乘。 + 而四元数空间中,对应的形式,是需要两边乘的,即 qpq^(-1) , 所以,对于旋转,右边的乘法旋转θ/2角, 左边的乘法旋转θ/2角,才能对应普通三维空间旋转θ角. + 那么,为什么是qpq^(-1)的形式? 因为四元数空间中谈三维空间中的旋转,必须把四元数r(任意的)先旋转到实分量为0的分空间中,即 xi+yj+zk空间中。所以,p不是矢量,而是对矢量r的变换。 而q是个表象变换(这里是坐标系的某种旋转)。 qpq^(-1)就是表象变换后,p变换的形式。 + +推荐参考[matthew-brett/transforms3d](https://github.com/matthew-brett/transforms3d/blob/main/transforms3d/euler.py)。以下是本人的移植c++版本: +```c++ +/* +* 移植Python的Transform3D库内容 +*/ +// axis sequences for Euler angles +QVector _NEXT_AXIS{1, 2, 0, 1}; + +QMap> _AXES2TUPLE{ + {QString("sxyz"), {0, 0, 0, 0}}, {QString("sxyx"), {0, 0, 1, 0}}, {QString("sxzy"), {0, 1, 0, 0}}, + {QString("sxzx"), {0, 1, 1, 0}}, {QString("syzx"), {1, 0, 0, 0}}, {QString("syzy"), {1, 0, 1, 0}}, + {QString("syxz"), {1, 1, 0, 0}}, {QString("syxy"), {1, 1, 1, 0}}, {QString("szxy"), {2, 0, 0, 0}}, + {QString("szxz"), {2, 0, 1, 0}}, {QString("szyx"), {2, 1, 0, 0}}, {QString("szyz"), {2, 1, 1, 0}} +}; + +QMap,QString> _TUPLE2AXES{ + {{0, 0, 0, 0},QString("sxyz")}, {{0, 0, 1, 0},QString("sxyx")}, {{0, 1, 0, 0},QString("sxzy")}, + {{0, 1, 1, 0},QString("sxzx")}, {{1, 0, 0, 0},QString("syzx")}, {{1, 0, 1, 0},QString("syzy")}, + {{1, 1, 0, 0},QString("syxz")}, {{1, 1, 1, 0},QString("syxy")}, {{2, 0, 0, 0},QString("szxy")}, + {{2, 0, 1, 0},QString("szxz")}, {{2, 1, 0, 0},QString("szyx")}, {{2, 1, 1, 0},QString("szyz")} +}; + +// For testing whether a number is close to zero +double _EPS4 = std::numeric_limits::epsilon() * 4.0; + +QVector> quat2mat(Quaternion q) +{ + double w = q.w; + double x = q.x; + double y = q.y; + double z = q.z; + + double Nq = w*w + x*x + y*y + z*z; + if (Nq < std::numeric_limits::epsilon()) + return {{1.0,0.0,0.0}, + {0.0,1.0,0.0}, + {0.0,0.0,1.0}}; + + double s = 2.0/Nq; + double X = x*s; + double Y = y*s; + double Z = z*s; + double wX = w*X;double wY = w*Y;double wZ = w*Z; + double xX = x*X;double xY = x*Y;double xZ = x*Z; + double yY = y*Y;double yZ = y*Z;double zZ = z*Z; + return {{ 1.0-(yY+zZ), xY-wZ, xZ+wY }, + { xY+wZ, 1.0-(xX+zZ), yZ-wX }, + { xZ-wY, yZ+wX, 1.0-(xX+yY) }}; +} + +QVector mat2euler(QVector> mat,const QString& axesStr) +{ + const QVector AXES=_AXES2TUPLE[axesStr]; + int firstaxis = AXES[0]; + int parity = AXES[1]; + int repetition = AXES[2]; + int frame = AXES[3]; + + int i = firstaxis; + int j = _NEXT_AXIS[i+parity]; + int k = _NEXT_AXIS[i-parity+1]; + + QVector> M = mat; + + double ax,ay,az; + if(repetition){ + double sy = qSqrt(M[i][j]*M[i][j] + M[i][k]*M[i][k]); + if(sy > _EPS4){ + ax = qAtan2( M[i][j], M[i][k]); + ay = qAtan2( sy, M[i][i]); + az = qAtan2( M[j][i], -M[k][i]); + }else{ + ax = qAtan2(-M[j][k], M[j][j]); + ay = qAtan2( sy, M[i][i]); + az = 0.0; + } + }else{ + double cy = qSqrt(M[i][i]*M[i][i] + M[j][i]*M[j][i]); + if(cy > _EPS4){ + ax = qAtan2( M[k][j], M[k][k]); + ay = qAtan2(-M[k][i], cy); + az = qAtan2( M[j][i], M[i][i]); + }else{ + ax = qAtan2(-M[j][k], M[j][j]); + ay = qAtan2(-M[k][i], cy); + az = 0.0; + } + } + + if(parity){ + ax = -ax; + ay = -ay; + az = -az; + } + if (frame){ + std::swap(ax,az); + } + + return {qRadiansToDegrees(ax), qRadiansToDegrees(ay), qRadiansToDegrees(az)}; +} + +Quaternion euler2quat(double ai,double aj,double ak,const QString& axesStr) +{ + ai = qDegreesToRadians(ai); + aj = qDegreesToRadians(aj); + ak = qDegreesToRadians(ak); + + const QVector AXES=_AXES2TUPLE[axesStr]; + int firstaxis = AXES[0]; + int parity = AXES[1]; + int repetition = AXES[2]; + int frame = AXES[3]; + + int i = firstaxis + 1; + int j = _NEXT_AXIS[i+parity-1] + 1; + int k = _NEXT_AXIS[i-parity] + 1; + + if(frame) + { + std::swap(ak,ai); + } + + if(parity) + { + aj= -aj; + } + + ai = ai / 2.0; + aj = aj / 2.0; + ak = ak / 2.0; + + double ci = qCos(ai); + double si = qSin(ai); + double cj = qCos(aj); + double sj = qSin(aj); + double ck = qCos(ak); + double sk = qSin(ak); + double cc = ci * ck; + double cs = ci * sk; + double sc = si * ck; + double ss = si * sk; + + QVector q(4); + if (repetition) + { + q[0] = cj*(cc - ss); + q[i] = cj*(cs + sc); + q[j] = sj*(cc + ss); + q[k] = sj*(cs - sc); + } + else + { + q[0] = cj*cc + sj*ss; + q[i] = cj*sc - sj*cs; + q[j] = cj*ss + sj*cc; + q[k] = cj*cs - sj*sc; + } + + if(parity) + { + q[j] *= -1.0; + } + + return Quaternion{q[0],q[1],q[2],q[3]}; +} + +QQuaternion euler2quaternion(double ai,double aj,double ak,const QString& axesStr) +{ + const Quaternion& q = euler2quat(ai,aj,ak,axesStr); + return QQuaternion(q.w,q.x,q.y,q.z); +} + +QVector quat2euler(Quaternion quaternion,const QString& axes) +{ + return mat2euler(quat2mat(quaternion), axes); +} + +QVector3D quaternion2euler(QQuaternion quaternion,const QString& axes) +{ + const QVector4D Vec=quaternion.toVector4D(); + Quaternion q{Vec.w(),Vec.x(),Vec.y(),Vec.z()}; + const QVector& Euler = quat2euler(q,axes); + return QVector3D(Euler[0],Euler[1],Euler[2]); +} +``` # FBX 四元数旋转顺序 xyz