四元数旋转不起作用

时间:2012-07-13 09:40:31

标签: android opengl-es

在Android的OpenGL ES 1中,我有一个由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)
  • 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度CCW产生

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

但我希望

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

我认为问题是resultVector(我使用的第二个向量来自视点的原点)不能正确转换。谁知道如何将世界坐标转换为对象坐标?谁知道当物体旋转时我们如何确定物体坐标?

2 个答案:

答案 0 :(得分:2)

昨天我决定编写Rubic Cube拼图,因为我过去尝试过的任何东西对我来说真的很不舒服,终于有了一些心情/时间来自己编写代码。我完成它已经是我的见解:

  1. Rubic Cube表示

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

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

    axises

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

    (0,0,0)
  2. <强> GUI

    我希望控制和观看尽可能接近真实的东西。因此,通过简单地点击目标子立方体(在reper cube[27]; // reper is transform matrix area0中)鼠标来控制旋转,然后从鼠标拖动的方向决定旋转哪个轴以及在哪个方向旋转。

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

  3. 如何修正更改本地坐标系?

    我提出了一个模糊的解决方案,我首先匹配每个坐标系的轴。为了检测哪个轴我只是查询方向的点积与变换矩阵的所有轴,并选择具有最高abs点积的轴。符号只是告诉坐标系是否相反(意味着旋转应该反转)。

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

     
    area1

    其中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坐标填充3D矢量。

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

     
    x,y,z

    与单位变换矩阵相比,两个函数都返回哪个轴是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; } 以及方向是否相反(sx,sy,sz)。

  4. 切片旋转

    这是谜题的核心。它绕轴切割简单。这用于动画,所以角度步长很小(我使用9度),但整个转弯必须是90度,否则Rubic Cube会破裂。

     
    x,y,z

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

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

  5. 在这里,您可以尝试我的演示: Win32+OpenGL Rubic Cube Demo

    这里有一些转动的动画GIF:

    animation

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

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

    axises

    每个命令由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(就像解决人类的真实立方体一样):

    solution steps

    1. 输入状态(不是步骤)
    2. 白色十字架,黄色中间(黄色中间朝前)
    3. 白色十字架(白色中间朝前)
    4. 白色角落(白色面朝下)
    5. 中间层(使用前3个命令)
    6. 顶层黄色交叉(使用第4个命令)
    7. 重新排序交叉,所以双方匹配(第5个命令)和重新排序角落(第6个命令)
    8. 将顶层角定向为完整立方体(第7个命令)
    9. 解算器很简单,可以在字符串上运行(未优化),所以它有点慢,但无论如何完整的解决方案在我的设置上只需要50毫秒。您可以在这里尝试升级的演示:

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

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

答案 1 :(得分:1)

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

由于您通常希望保留自己的坐标系,因此您可能需要考虑在立方体周围移动相机,而不是旋转立方体。我相信您可以在API或网络上找到“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度时,这种方法。

我希望这会有所帮助。