【问题标题】:Quaternion rotation does not work as excepted四元数旋转不能正常工作
【发布时间】:2012-07-13 09:40:31
【问题描述】:

在 OpenGL ES 1 for android 中,我有一个由 27 个较小的立方体组成的 Rubic 立方体。我想要导致特定小立方体正好在视点前面的旋转。所以我需要两个向量。一个是从对象的原点到特定立方体的向量。另一个是从原点到视点的向量。然后它们的叉积给我旋转轴,点积给我角度。

我将 (0,0,1)(从原点到世界坐标中的视点的向量)转换为对象坐标。这是代码:

    matrixGrabber.getCurrentModelView(gl);
    temporaryMatrix.set(matrixGrabber.mModelView);

    inputVector[0] = 0f; 
    inputVector[1] = 0f;
    inputVector[2] = 1f;
    inputVector[3] = 1f;
    Matrix.multiplyMV(resultVector, 0, temporaryMatrix.InvertMatrix(), 0, inputVector,0);
    resultVector[0]/=resultVector[3];
    resultVector[1]/=resultVector[3];
    resultVector[2]/=resultVector[3];

    inputVector = ..... // appropriate vector due to user-selection 

    axis = Vector.normalized(Vector.crossProduct(Vector.normalized(inputVector), Vector.normalized(resultVector)));
    degree = (float)Math.toDegrees(Math.acos(Vector.dot(Vector.normalized(inputVector), Vector.normalized(resultVector))));

我使用两个四元数进行旋转。每次用户选择一个动作时,应该发生一个旋转。这是代码:

    Quaternion currentRotation = new Quaternion();
    Quaternion temporaryRotation = new Quaternion();
    .
    .
    .
     currentRotation = (currentRotation).mulLeft(temporaryRotation.set(axis, degree));
     currentRotation.toMatrix(matrix);
     gl.glMatrixMode(GL10.GL_MODELVIEW);
     gl.glMultMatrixf(matrix, 0);

现在的问题是它在第一次旋转时效果很好。无论第一次轮换是什么。它运作良好,但对于下一次旋转,它似乎得到了错误的轴和度数。

例如,如果坐标系是

  • X-right (1,0,0)
  • 向上 (0,1,0)
  • Z-in (0,0,1)

然后第一次绕 X 90 度逆时针 (CCW) 旋转产生

  • X'-右 (1,0,0)
  • Y'-in (0,0,1)
  • Z'-down (0,-1,0)

第二次绕 Z 旋转 90 度 CCW 产生

  • X'-in (0,1,0)
  • Y'-左 (-1,0,0)
  • Z'-down (0,-1,0)

但我期待

  • 向上 (0,1,0)
  • Y-in (0,0,1)
  • Z-right(1,0,0)

我认为问题在于 resultVector(我使用的第二个从原点到视点的向量)没有正确转换。任何人都知道如何将世界坐标转换为对象坐标?有谁知道物体旋转后如何确定物体坐标?

【问题讨论】:

  • 我不能说我完全理解你的问题并且你没有发布任何输出。告诉我这是否是你的场景(尝试制作它):如果你的坐标系是 X-right (1,0 ,0), Y-up(0,1,0), Z-in(0,0,1) 然后首先应用围绕 X 90 度逆时针 (CCW) 的旋转结果应该是 X'-right(1,0 ,0), Y'-in(0,0,1), Z'-down(0,-1,0) 然后围绕 Z 90 度逆时针旋转现在你得到的是 X'-in(0,1, 0), Y'-左(-1,0,0), Z'-下(0,-1,0)。而你期望 X-up(0,1,0), Y-in(0,0,1), Z-right(1,0,0) 这是否描述了发生了什么?
  • @MaticOblak 是的,是的,你完全正确。这准确地描述了发生的事情。我该怎么办?为什么会这样?
  • @MaticOblak 我编辑了问题并在其中添加了您的示例。
  • @RaminZahedi 非常古老的问题,但无论如何我用我的方法添加了答案,希望它可以帮助某人......

标签: android opengl-es


【解决方案1】:

昨天我决定编写 Rubic Cube 拼图代码,因为我过去尝试过的任何游戏都让我感到非常不舒服,最后我终于有了一些心情/时间来自己编写代码。我已经完成了,这里是我的见解:

  1. 魔方表示

    我不认为四元数是一个好的选择。相反,我更喜欢:

    所以我最终得到了3*3*3=27 变换矩阵的列表以及一个额外的用于整个立方体旋转的矩阵。在起始状态下,所有子立方体都有单位旋转部分,并且原点设置为覆盖{ -1 , 0 ,+1 }的所有组合以填充整个魔方(每个子立方体网格的大小为1.0并以(0,0,0)为中心)

    我的多维数据集在 C++ 代码中定义如下:

    reper cube[27]; // reper is transform matrix
    
  2. 图形界面

    我希望控制和观看尽可能接近真实。所以旋转由鼠标控制,只需单击目标子立方体(在area0area1),然后从鼠标拖动的方向决定哪个轴和哪个方向旋转。

    从起始位置没有问题(因为即使您的代码也适用于此)。问题从下一次旋转开始(尤其是在改变旋转轴时),因为局部坐标系已经改变。全局视图旋转也是如此,因为它会搞砸所有这些。

  3. 如何修正局部坐标系的变化?

    我想出了一个模糊的解决方案,我首先匹配每个坐标系中的轴。为了检测哪个轴是哪个轴,我只需对查询的方向与变换矩阵的所有轴进行点积,然后选择绝对点积最高的轴。该符号只是说明坐标系是否相反(意味着应该反转旋转)。

    C++OpenGL 样式矩阵中,它看起来像这样:

    void RubiCube::axises_unit(reper &rep,int &x,int &y,int &z,int &sx,int &sy,int &sz)
        {
        int i;
        double p[3],xyz[3][3],a,b;
        rep.axisx_get(xyz[0]);
        rep.axisy_get(xyz[1]);
        rep.axisz_get(xyz[2]);
        vector_ld(p,1.0,0.0,0.0); for (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p); if (fabs(a)>=fabs(b)) { x=i; b=a; } } sx=+1; if (b<0) sx=-1;
        vector_ld(p,0.0,1.0,0.0); for (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p); if (fabs(a)>=fabs(b)) { y=i; b=a; } } sy=+1; if (b<0) sy=-1;
        vector_ld(p,0.0,0.0,1.0); for (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p); if (fabs(a)>=fabs(b)) { z=i; b=a; } } sz=+1; if (b<0) sz=-1;
        }
    

    其中reper 是包含直接和逆变换矩阵的类。 get_axis 只是在直接矩阵内部窥视并返回选定的轴方向单位向量。 vector_mul 是点积,vector_ld 只是用 x,y,z 坐标填充 3D 矢量。

    因为我还得到了与单位矩阵轴不对齐的全局立方体矩阵(因为它被旋转,所以视图看起来像上图)然后我需要对特殊向量(初始视图矩阵值)进行此轴匹配在我的情况下是这样的:

    void RubiCube::axises_obj(reper &rep,int &x,int &y,int &z,int &sx,int &sy,int &sz)
        {
        int i;
        double p[3],xyz[3][3],a,b;
        rep.axisx_get(xyz[0]);
        rep.axisy_get(xyz[1]);
        rep.axisz_get(xyz[2]);
        vector_ld(p,+0.707,-0.299,-0.641); for (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p); if (fabs(a)>=fabs(b)) { x=i; b=a; } } sx=+1; if (b<0) sx=-1;
        vector_ld(p,-0.000,-0.906,+0.423); for (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p); if (fabs(a)>=fabs(b)) { y=i; b=a; } } sy=+1; if (b<0) sy=-1;
        vector_ld(p,-0.707,-0.299,-0.641); for (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p); if (fabs(a)>=fabs(b)) { z=i; b=a; } } sz=+1; if (b<0) sz=-1;
        }
    

    两个函数都返回哪个轴是哪个x,y,z,以及与单位变换矩阵相比方向是否相反 (sx,sy,sz)。

  4. 切片旋转

    这是拼图的核心。它简单地绕轴转动切片。这用于动画,因此角度步长很小(我使用 9 度),但整个转弯必须总共 90 度,否则 Rubic Cube 会损坏。

    void RubiCube::cube_rotate(int axis,int slice,double ang)
        {
        int j,k,a[3],s[3];
        double p[3],p0[3]={0.0,0.0,0.0},lang;
        reper *r;
        _redraw=true;
        for (k=0;k<27;k++)
            {
            r=&cube[k];
            // local axis,sign
            axises_unit(*r,a[0],a[1],a[2],s[0],s[1],s[2]);
            // lang is local signed angle change
            lang=ang; if (s[axis]<0) lang=-lang;
            // select slice
            r->gpos_get(p);
            j=round(p[axis]+1.0);
            if (j!=slice) continue;
            // rotate global position
            if (axis==0) vector_rotx(p0,p,+ang);
            if (axis==1) vector_roty(p0,p,-ang);
            if (axis==2) vector_rotz(p0,p,+ang);
            r->gpos_set(p);
            // rotate local cube orientation
            if (a[axis]==0) r->lrotx(-lang);
            if (a[axis]==1) r->lroty(-lang);
            if (a[axis]==2) r->lrotz(-lang);
            }
        }
    

    reper::gpos_get 将矩阵原点返回为 3D 向量(点),reper::gpos_set 基本上设置了新的矩阵位置。 vector_rotx(p0,p,a) 将矢量 p 围绕 p0 和轴 x 旋转角度 a+/- 标志仅与 reper 类的旋转匹配(我在某处有所不同)。 reper::lrotx 围绕其本地 x 轴旋转 reper 以获取更多信息,请参阅第一个链接。

    如您所见,我直接使用每个矩阵原点坐标作为拓扑来选择切片立方体。

在这里你可以试试我的演示: Win32+OpenGL Rubic Cube Demo

这里是一些转弯的动画 gif:

[Edit1] 我在 RubiCube 中添加了简单的求解器

为了实现求解器,我添加了根据我的 RubiCube 内部表示计算的表面平面颜色图(在左侧...中间的正方形是我使用的边的名称和索引)。我还为求解器添加了内部命令 que(右侧的轴和方向):

每个命令由 2 个字符串表示:

edge slice  CW: R L U D F B
edge slice CCW: R'L'U'D'F'B'
mid  slice  CW: R0L0U0D0F0B0
whole cube  CW: RcLcUcDcFcBc

地图是这样的:

int map[6][3][3];

其中map[side][u][v] 包含侧面s、行u 和列v 的颜色。我实现了简单的7 steps solution(就像人类解决真正的立方体):

  1. 输入状态(不是步骤)
  2. 白十字黄中(黄中朝前)
  3. 白色十字(白色中间朝前)
  4. 白色边角(白色面朝下)
  5. 中间层(使用前 3 个命令)
  6. 顶层黄色十字(使用第四条命令)
  7. 重新排序交叉以使边匹配(第 5 个命令)并重新排序角(第 6 个命令)
  8. 定位顶层角以完成立方体(第 7 个命令)

求解器很简单,并且对字符串进行操作(未优化),所以它有点慢,但无论如何,完整的解决方案在我的设置中最多只需要 50 毫秒。您可以在这里尝试升级后的演示:

求解时可能仍有一些未定义的情况(由于代码中的错误或遗漏的情况)。在这种情况下,应用程序会粗略挂起(尚未实现看门狗)。密钥在包含的文本文件中进行了描述。

我使用了轻量级求解器(大约 300 行代码),因此找到的解决方案远非最优。例如,我只测试一个角而不是测试 4 个角,然后循环旋转立方体,导致不必要的转弯。其中一些后来被过滤掉了,但平均人类(我的)解决方案最多 200 圈,而这个求解器返回最多 300 圈(在最坏的情况下我发现到现在)。

【讨论】:

  • 非常全面和易于理解,漂亮的图形
  • @Rabbid76 thx ... 对我找到的所有立方体应用程序感到厌烦,所以我自己制作了一个有趣的应用程序:) 一个由鼠标直观处理的应用程序...
  • @Rabbid76 顺便说一句,所有的图形都是从演示中截取的......即使是旋转箭头Apply Rotation to Cylinder based on Tube Ending Normal我当时将它们添加到我的引擎中:)
  • 我正在浏览 stackoverflow 并在这里找到了您的帖子。干得好!我还曾经为 Windows 和 Android 平台开发过魔方。不幸的是,我有recovered only android one's。但我希望我有机会重构它并再次重写)))魔方是一个很棒的东西。我记得我在开发它时经历了很多乐趣。
  • @salvicode 自从发布这篇文章以来,我对我的魔方进行了一些更改(比如在求解器中修补一些错误和边缘情况),主要游戏类增长到 ~52.9 KB 的 C++ (GL/VCL) 萌芽状态做任何改变 3 年...
【解决方案2】:

发生的情况是,当您将此变换应用于模型(在您的情况下为旋转)时,您也会旋转它的基向量。把它想象成你也会旋转你的坐标系,或者你是从模型的第一人称视角看的。您所做的每一次转换都会影响下一次转换。

由于您通常希望保留自己的坐标系,因此您可能需要考虑围绕立方体移动相机而不是旋转立方体。我相信您可以在您的 API 或 Web 上找到“lookAt”方法。它应该采用 3 个向量:cameraPosition、lookAtPoint、upVector。使用这种方法,您可以将立方体定位到 (0,0,0),这也是您的“lookAtPoint”,第一个 cameraPosition 应该类似于 (0,0,-1),第一个 upVector 到 (0,1,0)。现在进行运动(您可能只使用左/右和上/下作为输入): 要上/下(围绕 X 旋转),您接下来执行以下操作:

originalDistance = (cameraPosition-objectPosition).lenght
leftVector = normalizedVector(crossProduct(camearPosition, upVector))//generaly cameraPosition-objectPosition
camearPosition = cameraPosition + upVector*inputScalar //inputScalar should be a small floating value
cameraPosition = normalizedVector(cameraPosition)*originalDistance //put camera to original distance from object
upVector = normalizedVector(crossProduct(cameraPosition, leftVector))//generaly cameraPosition-objectPosition

要向左/向右(围绕 X 旋转),您接下来执行以下操作:

originalDistance = (cameraPosition-objectPosition).lenght
leftVector = normalizedVector(crossProduct(camearPosition, upVector))//generaly cameraPosition-objectPosition
camearPosition = cameraPosition + leftVector*inputScalar //inputScalar should be a small floating value
cameraPosition = normalizedVector(cameraPosition)*originalDistance //put camera to original distance from object
leftVector = normalizedVector(crossProduct(cameraPosition, upVector))//generaly cameraPosition-objectPosition
upVector = normalizedVector(crossProduct(cameraPosition, leftVector))//generaly cameraPosition-objectPosition

这通常应该可以解决问题..(如果我写错了,请告诉我)

至于您旋转对象本身的方法,您应该找出对象自身坐标系中的四元数并围绕该坐标系旋转它。如果你有一些数学技能,这也很容易。除此之外,您还可以只定义 2 个角度 (X,Y) 并通过输入直接更改它们并使用 (1,0,0,X) 和 (0,1,0,Y) 的四元数,但可能存在问题当 Y 为 90 度时这种方法..

我希望这会有所帮助。

【讨论】:

  • 非常感谢您的努力。但实际上你的回答并没有解决我的问题。我知道基向量也随着对象本身旋转,这就是为什么我通过将第二个向量(来自原点的向量)乘以 modelViewMatrix 的倒数来转换它。
  • 我认为问题在于 resultVector(我使用的第二个从原点到视点的向量)没有正确转换。你知道如何将世界坐标转换为物体坐标吗?你知道物体旋转后如何确定物体坐标吗?
  • 它只适用于第一次旋转。我完全糊涂了。为什么它不适用于下一个?如果有问题为什么它适用于第一个?你知道第一个它也应该转换矢量,因为我已经实现了 ArcBall 旋转,用户可以通过移动操作旋转立方体,当用户点击一个应该旋转的特定立方体时。
  • 它适用于第一个,因为模型矩阵是恒等的。它实际上适用于所有旋转,而不是您想要的方式。你得到的结果很正常。无论如何,我想你可以使用模型矩阵的逆矩阵并用它来乘以你的四元数以获得正确的旋转。但请注意,您需要先松开模型矩阵的平移部分。
猜你喜欢
  • 2015-06-13
  • 1970-01-01
  • 1970-01-01
  • 2016-01-02
  • 2017-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多