C++ 使用GLM的基于四元数的点旋转

C++ 使用GLM的基于四元数的点旋转,c++,math,rotation,quaternions,glm-math,C++,Math,Rotation,Quaternions,Glm Math,我正在尝试使用GLM中实现的四元数旋转一个点。最终目标是使用此代码创建轨道相机,但这是一个旁注,有助于理解代码背后的动机 为了更好地理解基于四元数的旋转,我编写了一些包含两个循环的代码。第一个循环将通过绕X轴逐步旋转四元数到90度来增量更改四元数的方向,第二个循环将继续绕Z轴逐步旋转到90度。每个循环执行4个步骤。因此,每个回路围绕各自的轴增量旋转90/4=22.5度。使用四元数乘法应用方向变化,并使用Euler角跟踪方向变化。循环应以四元数结束,四元数将在(0,0,3)到(3,0,0)之间旋转

我正在尝试使用GLM中实现的四元数旋转一个点。最终目标是使用此代码创建轨道相机,但这是一个旁注,有助于理解代码背后的动机

为了更好地理解基于四元数的旋转,我编写了一些包含两个循环的代码。第一个循环将通过绕X轴逐步旋转四元数到90度来增量更改四元数的方向,第二个循环将继续绕Z轴逐步旋转到90度。每个循环执行4个步骤。因此,每个回路围绕各自的轴增量旋转90/4=22.5度。使用四元数乘法应用方向变化,并使用Euler角跟踪方向变化。循环应以四元数结束,四元数将在(0,0,3)到(3,0,0)之间旋转一个点。请注意,我不仅仅是想确定做这个旋转的四元数。目标是执行一系列增量旋转

如果我们看下图,从C到I的转换发生在第一个循环中,然后从I到R的转换发生在第二个循环中(请原谅稀疏点命名)

点的旋转定义为(请参见和):

式中,v应视为纯四元数(具有零标量项w),q应为单位四元数(长度为1)。从我的理解来看,右手乘以四元数的倒数,需要在3D空间中保持结果v’,而不是以4D向量结束。所以v'也需要是一个纯四元数

然后是旋转的倍增效应,左手与q相乘贡献了一半的期望旋转,右手与逆相乘增加了另一半的期望旋转

本·厄特(Ben Eater)和格兰特·桑德森(Grant Sanderson)对四元数进行了出色的交互式可视化和解释,我将其用作交叉参考。可以找到它

因此,我们首先需要使用一个绕X轴旋转11.25度的四元数,GLM返回欧拉角的四元数(使用四元数符号[w,[X,y,z]:

根据,并且由于我们纯粹围绕X轴旋转,我们可以通过对四元数的w分量执行acos来验证GLM计算的四元数中的旋转量:

float angle = acosf(q.w)
然后:

这是所需角度的一半。。。互动动画的交叉检查也证实了这一点(请原谅舍入):

因此,GLM返回的四元数为11.25度,实际上旋转了所需角度的一半。。。如果我们看一下GLM代码,从Euler角度计算w分量会稍微复杂一些,因为旋转可以围绕任意旋转轴发生。。。但欧拉角明显减半:

template <typename T, precision P>
GLM_FUNC_QUALIFIER tquat<T, P>::tquat(tvec3<T, P> const & eulerAngle)
{
    tvec3<T, P> c = glm::cos(eulerAngle * T(0.5));
    tvec3<T, P> s = glm::sin(eulerAngle * T(0.5));
    
    this->w = c.x * c.y * c.z + s.x * s.y * s.z;
    this->x = s.x * c.y * c.z - c.x * s.y * s.z;
    this->y = c.x * s.y * c.z + s.x * c.y * s.z;
    this->z = c.x * c.y * s.z - s.x * s.y * c.z;
}
我得到了我所希望的结果。该点正确地遵循了所有中间步骤,从(0,0,3)到(3,0,0)。此外,所有中间步骤的w分量均为0

但是如果我使用旋转的“正确形式”,用左手乘以q,右手乘以q的倒数(用11.25度的半步来解释旋转的加倍):

当第二个循环开始绕Z轴旋转点时,我开始得到错误的结果。一个很小但很明显的Z分量开始向内蠕动,旋转距离22.5度的完整步距很近。这在下图中的绿点中可见

对于两种旋转方法,旋转点的w分量保持为0

有人能解释为什么GLM旋转在从左到右的一次乘法中工作正常吗?

这是一种将操作数量降至最低的优化吗

我可以使用GLM中的
v'=q*v
旋转来获得所有旋转的一致和正确结果吗?

代码:


我有我的问题的答案和一个正在工作的轨道相机,但还没有时间再次检查示例代码现在是否正常工作-应该可以

第一个问题是为什么GLM在四元数转换过程中会将角度减半,而且看起来像是根据。。。这是必须的。这一部分可以经受更多的调查,但由于时间不够,我不得不接受它

GLM中的矢量旋转是使用乘法运算符实现的。这意味着,当向量3和四元数相乘时,不会将向量转换为四元数,然后执行乘法,它将执行以下操作:

或者在C++中:

vec3 posOrigin;
quat rotQ;
...
vec3 posRot = rotQ * posOrigin;
这段代码实际上并没有进行直接的四元数乘法。它做一个旋转。我个人更喜欢GLM提供的函数调用,比如
rotate(quat,vec)
。。。但我确信操作符重载混淆是有原因的

还请注意,操作数顺序很重要,因为向量和四元数之间的乘法定义如下:

v' = q * v
template <typename T, precision P>
GLM_FUNC_QUALIFIER tvec3<T, P> operator*(tvec3<T, P> const & v, tquat<T, P> const & q)
{
    return glm::inverse(q) * v;
}
模板
GLM_FUNC_限定符tvec3运算符*(tvec3常量和v,tquat常量和q)
{
返回glm::逆(q)*v;
}
因此将以相反的方向旋转向量

请注意,GLM还实现四元数之间的乘法,但为此,需要使用两个四元数之间的乘法运算符:

template <typename T, precision P>
template <typename U>
GLM_FUNC_QUALIFIER tquat<T, P> & tquat<T, P>::operator*=(tquat<U, P> const & r)
{
    tquat<T, P> const p(*this);
    tquat<T, P> const q(r);

    this->w = p.w * q.w - p.x * q.x - p.y * q.y - p.z * q.z;
    this->x = p.w * q.x + p.x * q.w + p.y * q.z - p.z * q.y;
    this->y = p.w * q.y + p.y * q.w + p.z * q.x - p.x * q.z;
    this->z = p.w * q.z + p.z * q.w + p.x * q.y - p.y * q.x;
    return *this;
}
模板
模板
GLM_FUNC_限定符tquat&tquat::运算符*=(tquat常量&r)
{
tquat常数p(*本);
tquat常数q(r);
这->w=p.w*q.w-p.x*q.x-p.y*q.y-p.z*q.z;
这个->x=p.w*q.x+p.x*q.w+p.y*q.z-p.z*q.y;
这->y=p.w*q.y+p.y*q.w+p.z*q.x-p.x*q.z;
这个->z=p.w*q.z+p.z*q.w+p.x*q.y-p.y*q.x;
归还*这个;
}
因为GLM有珍贵的小文件
v' = q * v * q^-1
const int rotSteps = 4;
// Rotate around X axis in steps to 90deg
vec3 eulerState = vec3(0.0f);
// point we want to rotate (use vec4 to track the w component during rotations)
vec4 v = vec4(0.0f, 0.0f, 3.0f, 0.0f);

// Full Euler steps for q * v rotation
quat orientF   = quat(1.0f, 0.0f, 0.0f, 0.0f);
vec3 euler     = vec3(RAD(90.0f), RAD(0.0f), RAD(0.0f));
vec3 eulerStep = euler / (float)rotSteps;
quat qEulerF   = quat(eulerStep); // GetRotQuat(eulerStep);

vec4 qa          = ToAngularForm(qEulerF);
vec3 orientEuler = eulerAngles(qEulerF);
CLogD(TAG, "Rot Full Step    Q [W, X, Y, Z]: " FMT_Q(4)  " / " FMT_V3(2) "deg / " FMT_QA(2), PAR_Q(qEulerF), PAR_V3(degrees(orientEuler)), PAR_QA(qa));

// Half Euler steps for q * v * q^-1 rotation
quat orientH    = quat(1.0f, 0.0f, 0.0f, 0.0f);
vec3 eulerStepH = eulerStep / 2.0f;
quat qEulerH    = quat(eulerStepH); // GetRotQuat(eulerStepH);

qa          = ToAngularForm(qEulerH);
orientEuler = eulerAngles(qEulerH);
CLogD(TAG, "Rot Half Step    Q [W, X, Y, Z]: " FMT_Q(4) " / " FMT_V3(2) "deg / " FMT_QA(2), PAR_Q(qEulerH), PAR_V3(degrees(orientEuler)), PAR_QA(qa));

quat qEulerHI = inverse(qEulerH);
vec4 qai      = ToAngularForm(qEulerHI);
orientEuler   = eulerAngles(qEulerHI);
CLogD(TAG, "Rot Half Step Q^-1 [W, X, Y, Z]: " FMT_Q(4) " / " FMT_V3(2) "deg / " FMT_QA(2), PAR_Q(qEulerHI), PAR_V3(degrees(orientEuler)), PAR_QA(qai));


for (int rotStep = 1; rotStep <= rotSteps; ++rotStep)
{
    // Track the absolute Euler rotation
    eulerState += eulerStep;
    // Rotate by incremental rotation as defined by Euler angles
    orientH = qEulerH * orientH;
    orientEuler = eulerAngles(orientH);
    CLogI(TAG, "Rot Step %d. Curr Abs Q: " FMT_Q(4) "/" FMT_V3(2) "deg, Abs Euler: " FMT_V3(2) "deg",
          rotStep, PAR_Q(orientH), PAR_V3(degrees(orientEuler)), PAR_V3(degrees(eulerState)));

    // Transform the point using the correct q * v * q^-1 rotation and multiply from Left and Right
    quat orientHI = inverse(orientH);
    qa  = ToAngularForm(orientH);
    qai = ToAngularForm(orientHI);

    vec4 rotV = orientH * v * orientHI;
    CLogD(TAG, "Rot      QL: " FMT_Q(4) " / " FMT_QA(1), PAR_Q(orientH), PAR_QA(qa));
    CLogD(TAG, "Rot      QR: " FMT_Q(4) " / " FMT_QA(1), PAR_Q(orientHI), PAR_QA(qai));
    CLogD(TAG, "Rot LR   -> " FMT_V4(1), PAR_V4(rotV));

    // Transform the point using the incorrect q * v rotation and multiply from Left only
    orientF = qEulerF * orientF;
    qa      = ToAngularForm(orientF);

    rotV = orientF * v;
    CLogD(TAG, "Rot      QR: " FMT_Q(4) " / " FMT_QA(1), PAR_Q(orientF), PAR_QA(qa));
    CLogD(TAG, "Rot L    -> " FMT_V4(1), PAR_V4(rotV));
}


// Rotate for 90 degrees around the Z axis
// Full Euler steps for q * v rotation
euler = vec3(RAD(0.0f), RAD(0.0f), RAD(90.0f));
eulerStep = euler / (float)rotSteps;
qEulerF = quat(eulerStep); // GetRotQuat(eulerStep);

qa = ToAngularForm(qEulerF);
orientEuler = eulerAngles(qEulerF);
CLogD(TAG, "Rot Full Step    Q [W, X, Y, Z]: " FMT_Q(4)  " / " FMT_V3(2) "deg / " FMT_QA(2), PAR_Q(qEulerF), PAR_V3(degrees(orientEuler)), PAR_QA(qa));

// Half Euler steps for q * v * q^-1 rotation
eulerStepH = eulerStep / 2.0f;
qEulerH = quat(eulerStepH); // GetRotQuat(eulerStepH);

qa = ToAngularForm(qEulerH);
orientEuler = eulerAngles(qEulerH);
CLogD(TAG, "Rot Half Step    Q [W, X, Y, Z]: " FMT_Q(4) " / " FMT_V3(2) "deg / " FMT_QA(2), PAR_Q(qEulerH), PAR_V3(degrees(orientEuler)), PAR_QA(qa));

qEulerHI = inverse(qEulerH);
qai = ToAngularForm(qEulerHI);
orientEuler = eulerAngles(qEulerHI);
CLogD(TAG, "Rot Half Step Q^-1 [W, X, Y, Z]: " FMT_Q(4) " / " FMT_V3(2) "deg / " FMT_QA(2), PAR_Q(qEulerHI), PAR_V3(degrees(orientEuler)), PAR_QA(qai));


for (int rotStep = 1; rotStep <= rotSteps; ++rotStep)
{
    // Track the absolute Euler rotation
    eulerState += eulerStep;
    // Rotate by incremental rotation as defined by Euler angles
    orientH = qEulerH * orientH;
    orientEuler = eulerAngles(orientH);
    CLogI(TAG, "Rot Step %d. Curr Abs Q: " FMT_Q(4) "/" FMT_V3(2) "deg, Abs Euler: " FMT_V3(2) "deg",
        rotStep, PAR_Q(orientH), PAR_V3(degrees(orientEuler)), PAR_V3(degrees(eulerState)));

    // Transform the point using the correct q * v * q^-1 rotation and multiply from Left and Right
    quat orientHI = inverse(orientH);
    qa = ToAngularForm(orientH);
    qai = ToAngularForm(orientHI);

    vec4 rotV = orientH * v * orientHI;
    CLogD(TAG, "Rot      QL: " FMT_Q(4) " / " FMT_QA(1), PAR_Q(orientH), PAR_QA(qa));
    CLogD(TAG, "Rot      QR: " FMT_Q(4) " / " FMT_QA(1), PAR_Q(orientHI), PAR_QA(qai));
    CLogD(TAG, "Rot LR   -> " FMT_V4(1), PAR_V4(rotV));

    // Transform the point using the incorrect q * v rotation and multiply from Left only
    orientF = qEulerF * orientF;
    qa = ToAngularForm(orientF);

    rotV = orientF * v;
    CLogD(TAG, "Rot      QR: " FMT_Q(4) " / " FMT_QA(1), PAR_Q(orientF), PAR_QA(qa));
    CLogD(TAG, "Rot L    -> " FMT_V4(1), PAR_V4(rotV));
}
Rot Full Step    Q [W, X, Y, Z]: [ 0.9808, [ 0.1951,  0.0000,  0.0000]] / [ 22.50, -0.00,  0.00]deg / cos( 11.25) + sin( 11.25)( 1.00i +  0.00j +  0.00k)
Rot Half Step    Q [W, X, Y, Z]: [ 0.9952, [ 0.0980,  0.0000,  0.0000]] / [ 11.25, -0.00,  0.00]deg / cos( 5.63) + sin( 5.63)( 1.00i +  0.00j +  0.00k)
Rot Half Step Q^-1 [W, X, Y, Z]: [ 0.9952, [-0.0980, -0.0000, -0.0000]] / [-11.25, -0.00,  0.00]deg / cos( 5.63) + sin( 5.63)(-1.00i + -0.00j + -0.00k)
Rot Step 1. Curr Abs Q: [ 0.9952, [ 0.0980,  0.0000,  0.0000]]/[ 11.25, -0.00,  0.00]deg, Abs Euler: [ 22.50,  0.00,  0.00]deg
Rot      QL: [ 0.9952, [ 0.0980,  0.0000,  0.0000]] / cos( 5.6) + sin( 5.6)( 1.0i +  0.0j +  0.0k)
Rot      QR: [ 0.9952, [-0.0980, -0.0000, -0.0000]] / cos( 5.6) + sin( 5.6)(-1.0i + -0.0j + -0.0k)
Rot LR   -> [ 0.0, -1.1,  2.8,  0.0]
Rot      QR: [ 0.9808, [ 0.1951,  0.0000,  0.0000]] / cos( 11.3) + sin( 11.3)( 1.0i +  0.0j +  0.0k)
Rot L    -> [ 0.0, -1.1,  2.8,  0.0]
Rot Step 2. Curr Abs Q: [ 0.9808, [ 0.1951,  0.0000,  0.0000]]/[ 22.50, -0.00,  0.00]deg, Abs Euler: [ 45.00,  0.00,  0.00]deg
Rot      QL: [ 0.9808, [ 0.1951,  0.0000,  0.0000]] / cos( 11.3) + sin( 11.3)( 1.0i +  0.0j +  0.0k)
Rot      QR: [ 0.9808, [-0.1951, -0.0000, -0.0000]] / cos( 11.2) + sin( 11.2)(-1.0i + -0.0j + -0.0k)
Rot LR   -> [ 0.0, -2.1,  2.1,  0.0]
Rot      QR: [ 0.9239, [ 0.3827,  0.0000,  0.0000]] / cos( 22.5) + sin( 22.5)( 1.0i +  0.0j +  0.0k)
Rot L    -> [ 0.0, -2.1,  2.1,  0.0]
Rot Step 3. Curr Abs Q: [ 0.9569, [ 0.2903,  0.0000,  0.0000]]/[ 33.75, -0.00,  0.00]deg, Abs Euler: [ 67.50,  0.00,  0.00]deg
Rot      QL: [ 0.9569, [ 0.2903,  0.0000,  0.0000]] / cos( 16.9) + sin( 16.9)( 1.0i +  0.0j +  0.0k)
Rot      QR: [ 0.9569, [-0.2903, -0.0000, -0.0000]] / cos( 16.9) + sin( 16.9)(-1.0i + -0.0j + -0.0k)
Rot LR   -> [ 0.0, -2.8,  1.1,  0.0]
Rot      QR: [ 0.8315, [ 0.5556,  0.0000,  0.0000]] / cos( 33.8) + sin( 33.8)( 1.0i +  0.0j +  0.0k)
Rot L    -> [ 0.0, -2.8,  1.1,  0.0]
Rot Step 4. Curr Abs Q: [ 0.9239, [ 0.3827,  0.0000,  0.0000]]/[ 45.00, -0.00,  0.00]deg, Abs Euler: [ 90.00,  0.00,  0.00]deg
Rot      QL: [ 0.9239, [ 0.3827,  0.0000,  0.0000]] / cos( 22.5) + sin( 22.5)( 1.0i +  0.0j +  0.0k)
Rot      QR: [ 0.9239, [-0.3827, -0.0000, -0.0000]] / cos( 22.5) + sin( 22.5)(-1.0i + -0.0j + -0.0k)
Rot LR   -> [ 0.0, -3.0,  0.0,  0.0]
Rot      QR: [ 0.7071, [ 0.7071,  0.0000,  0.0000]] / cos( 45.0) + sin( 45.0)( 1.0i +  0.0j +  0.0k)
Rot L    -> [ 0.0, -3.0,  0.0,  0.0]

Rot Full Step    Q [W, X, Y, Z]: [ 0.9808, [ 0.0000,  0.0000,  0.1951]] / [ 0.00, -0.00,  22.50]deg / cos( 11.25) + sin( 11.25)( 0.00i +  0.00j +  1.00k)
Rot Half Step    Q [W, X, Y, Z]: [ 0.9952, [ 0.0000,  0.0000,  0.0980]] / [ 0.00, -0.00,  11.25]deg / cos( 5.63) + sin( 5.63)( 0.00i +  0.00j +  1.00k)
Rot Half Step Q^-1 [W, X, Y, Z]: [ 0.9952, [-0.0000, -0.0000, -0.0980]] / [ 0.00, -0.00, -11.25]deg / cos( 5.63) + sin( 5.63)(-0.00i + -0.00j + -1.00k)
Rot Step 1. Curr Abs Q: [ 0.9194, [ 0.3808,  0.0375,  0.0906]]/[ 45.00,  0.00,  11.25]deg, Abs Euler: [ 90.00,  0.00,  22.50]deg
Rot      QL: [ 0.9194, [ 0.3808,  0.0375,  0.0906]] / cos( 23.2) + sin( 23.2)( 1.0i +  0.1j +  0.2k)
Rot      QR: [ 0.9194, [-0.3808, -0.0375, -0.0906]] / cos( 23.2) + sin( 23.2)(-1.0i + -0.1j + -0.2k)
Rot LR   -> [ 1.0, -2.8,  0.0,  0.0]
Rot      QR: [ 0.6935, [ 0.6935,  0.1379,  0.1379]] / cos( 46.1) + sin( 46.1)( 1.0i +  0.2j +  0.2k)
Rot L    -> [ 1.1, -2.8,  0.0,  0.0]
Rot Step 2. Curr Abs Q: [ 0.9061, [ 0.3753,  0.0747,  0.1802]]/[ 45.00, -0.00,  22.50]deg, Abs Euler: [ 90.00,  0.00,  45.00]deg
Rot      QL: [ 0.9061, [ 0.3753,  0.0747,  0.1802]] / cos( 25.0) + sin( 25.0)( 0.9i +  0.2j +  0.4k)
Rot      QR: [ 0.9061, [-0.3753, -0.0747, -0.1802]] / cos( 25.0) + sin( 25.0)(-0.9i + -0.2j + -0.4k)
Rot LR   -> [ 1.9, -2.4,  0.1,  0.0]
Rot      QR: [ 0.6533, [ 0.6533,  0.2706,  0.2706]] / cos( 49.2) + sin( 49.2)( 0.9i +  0.4j +  0.4k)
Rot L    -> [ 2.1, -2.1,  0.0,  0.0]
Rot Step 3. Curr Abs Q: [ 0.8841, [ 0.3662,  0.1111,  0.2682]]/[ 45.00,  0.00,  33.75]deg, Abs Euler: [ 90.00,  0.00,  67.50]deg
Rot      QL: [ 0.8841, [ 0.3662,  0.1111,  0.2682]] / cos( 27.9) + sin( 27.9)( 0.8i +  0.2j +  0.6k)
Rot      QR: [ 0.8841, [-0.3662, -0.1111, -0.2682]] / cos( 27.9) + sin( 27.9)(-0.8i + -0.2j + -0.6k)
Rot LR   -> [ 2.5, -1.6,  0.3,  0.0]
Rot      QR: [ 0.5879, [ 0.5879,  0.3928,  0.3928]] / cos( 54.0) + sin( 54.0)( 0.7i +  0.5j +  0.5k)
Rot L    -> [ 2.8, -1.1,  0.0,  0.0]
Rot Step 4. Curr Abs Q: [ 0.8536, [ 0.3536,  0.1464,  0.3536]]/[ 45.00,  0.00,  45.00]deg, Abs Euler: [ 90.00,  0.00,  90.00]deg
Rot      QL: [ 0.8536, [ 0.3536,  0.1464,  0.3536]] / cos( 31.4) + sin( 31.4)( 0.7i +  0.3j +  0.7k)
Rot      QR: [ 0.8536, [-0.3536, -0.1464, -0.3536]] / cos( 31.4) + sin( 31.4)(-0.7i + -0.3j + -0.7k)
Rot LR   -> [ 2.9, -0.7,  0.4,  0.0]
Rot      QR: [ 0.5000, [ 0.5000,  0.5000,  0.5000]] / cos( 60.0) + sin( 60.0)( 0.6i +  0.6j +  0.6k)
Rot L    -> [ 3.0,  0.0,  0.0,  0.0]
template <typename T, precision P>
GLM_FUNC_QUALIFIER tvec3<T, P> operator*(tquat<T, P> const & q, tvec3<T, P> const & v)
{
    tvec3<T, P> const QuatVector(q.x, q.y, q.z);
    tvec3<T, P> const uv(glm::cross(QuatVector, v));
    tvec3<T, P> const uuv(glm::cross(QuatVector, uv));

    return v + ((uv * q.w) + uuv) * static_cast<T>(2);
}
v' = q * v
vec3 posOrigin;
quat rotQ;
...
vec3 posRot = rotQ * posOrigin;
template <typename T, precision P>
GLM_FUNC_QUALIFIER tvec3<T, P> operator*(tvec3<T, P> const & v, tquat<T, P> const & q)
{
    return glm::inverse(q) * v;
}
template <typename T, precision P>
template <typename U>
GLM_FUNC_QUALIFIER tquat<T, P> & tquat<T, P>::operator*=(tquat<U, P> const & r)
{
    tquat<T, P> const p(*this);
    tquat<T, P> const q(r);

    this->w = p.w * q.w - p.x * q.x - p.y * q.y - p.z * q.z;
    this->x = p.w * q.x + p.x * q.w + p.y * q.z - p.z * q.y;
    this->y = p.w * q.y + p.y * q.w + p.z * q.x - p.x * q.z;
    this->z = p.w * q.z + p.z * q.w + p.x * q.y - p.y * q.x;
    return *this;
}