四元数旋转不能正常工作 [英] Quaternion rotation does not work as excepted

查看:16
本文介绍了四元数旋转不能正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

 matrixGrabber.getCurrentModelView(gl);临时Matrix.set(matrixGrabber.mModelView);输入向量[0] = 0f;输入向量[1] = 0f;输入向量[2] = 1f;输入向量[3] = 1f;Matrix.multiplyMV(resultVector, 0,temporaryMatrix.InvertMatrix(), 0, inputVector,0);结果向量[0]/=结果向量[3];结果向量[1]/=结果向量[3];结果向量[2]/=结果向量[3];inputVector = .....//由于用户选择合适的向量轴 = Vector.normalized(Vector.crossProduct(Vector.normalized(inputVector), Vector.normalized(resultVector)));degree = (float)Math.toDegrees(Math.acos(Vector.dot(Vector.normalized(inputVector), Vector.normalized(resultVector))));

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

 四元数 currentRotation = new Quaternion();四元数temporaryRotation = new Quaternion();...currentRotation = (currentRotation).mulLeft(temporaryRotation.set(axis, degree));currentRotation.toMatrix(矩阵);gl.glMatrixMode(GL10.GL_MODELVIEW);gl.glMultMatrixf(矩阵, 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(我使用的从原点到视点的第二个向量)没有正确转换.任何人都知道如何将世界坐标转换为对象坐标?有谁知道物体旋转后如何确定物体坐标?

解决方案

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

  1. 魔方表示

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

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

      reper 立方体[27];//reper 是变换矩阵

    • 图形界面

      我希望控制和观看尽可能接近真实.因此,旋转由鼠标通过简单地点击目标子立方体(在 area0area1 中)来控制,然后从鼠标拖动的方向决定哪个轴被旋转和在哪个方向.

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

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

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

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

      void RubiCube::axises_unit(reper &rep,int &x,int &y,int &z,int &sx,int &sy,int &sz){诠释我;双 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);对于 (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;如果(b<0)sx=-1;矢量_ld(p,0.0,1.0,0.0);对于 (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p);如果 (fabs(a)>=fabs(b)) { y=i;b=a;} } sy=+1;如果(b<0)sy=-1;vector_ld(p,0.0,0.0,1.0);对于 (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;如果(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){诠释我;双 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);对于 (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;如果(b<0)sx=-1;vector_ld(p,-0.000,-0.906,+0.423);对于 (b=0.0,i=0;i<3;i++) { a=vector_mul(xyz[i],p);如果 (fabs(a)>=fabs(b)) { y=i;b=a;} } sy=+1;如果(b<0)sy=-1;vector_ld(p,-0.707,-0.299,-0.641);对于 (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;如果(b<0)sz=-1;}

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

    • 切片旋转

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

      void RubiCube::cube_rotate(int axis,int slice,double ang){整数 j,k,a[3],s[3];双 p[3],p0[3]={0.0,0.0,0.0},lang;重复 *r;_重绘=真;对于 (k=0;k<27;k++){r=&立方[k];//局部坐标轴,符号axis_unit(*r,a[0],a[1],a[2],s[0],s[1],s[2]);//lang 是局部有符号角度变化朗=昂;if (s[axis]<0) lang=-lang;//选择切片r->gpos_get(p);j=圆形(p[轴]+1.0);如果 (j!=slice) 继续;//旋转全局位置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);//旋转局部立方体方向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 旋转角度 一个.+/- 符号仅用于匹配 reper 类的旋转(我在某处有所不同).reper::lrotx 围绕其本地 x 轴旋转 reper 以了解更多信息,请参阅第一个链接.

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

在这里你可以试试我的演示:

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

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

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

边缘切片 CW: R L U D F B边缘切片 CCW:R'L'U'D'F'B'中间片CW:R0L0U0D0F0B0整个立方体 CW:RcLcUcDcFcBc

地图是这样的:

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

其中 map[side][u][v] 包含边 s、行 u 和列 的正方形颜色v.我实现了简单的

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

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

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

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

In OpenGL ES 1 for android, I have a Rubic cube that consists of 27 smaller cubes. i want rotations which cause particular small cube becoming exactly in front of the viewpoint. so I need two vectors. one is the vector that comes from the origin of the object to a particular cube. and another is the vector that comes from origin to the viewpoint. then the cross product of them gives me the axis of the rotation and the dot product gives me the angle.

I convert the (0,0,1) -which is the vector that comes from the origin to the viewpoint in world coordinate- to object coordinates. here is the code:

    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))));

I use two Quaternions for rotations. each time user choose an action one of that rotations should happen. here is the code :

    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);

now the problem is that it just works fine for the first rotation. whatever the first rotation would be. it works well but for the next rotations it seems that it gets wrong axis and degree.

For example if the coordinate system would be

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

then first rotation around X 90 degrees counter clockwise (CCW) produces

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

and second rotation around Z 90 degrees CCW produces

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

but I expect

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

I think the problem is that the resultVector(the second vector which I used that comes from origin toward the viewpoint) does not convert properly. anyone knows how can I convert the world coordinate to object coordinate? anyone knows how can we determine object coordinates when object have rotated?

解决方案

Well yesterday I decided to code Rubic Cube puzzle because any I tried in past was really uncomfortable for me and finally got some mood/time to code it myself. As I finished it already here are mine insights:

  1. Rubic Cube representation

    I do not see quaternions as a good choice for this. Instead I am more comfortable with:

    So I ended up with list of 3*3*3=27 transform matrices plus one additional for whole cube rotations. In starting state all the sub cubes have unit rotation part and the origins are set to cover all the combinations of { -1 , 0 ,+1 } to fill the whole Rubic Cube (size of each sub cube mesh is 1.0 and centered around (0,0,0))

    My cubes are in C++ code defines like this:

    reper cube[27]; // reper is transform matrix
    

  2. GUI

    I wanted the controlling and viewing to be as close to the real thing as I could. So the rotations are controlled by mouse by simple click on target sub-cube (in area0 or area1) and then from direction of mouse drag it is decided which axis is rotated and in which direction.

    From starting position there is no problem (as even your code works well for that). The problems start on next rotation (especially when changing axis of rotation) because the local coordinate systems already changed. The same goes for global view rotation as it will mess up all of this.

  3. How to remedy changing local coordinate system?

    I come up with a obscure solution where I first match axis from each coordinate system. To detect which axis is which I simply do a dot product of queried direction vs. all axises of the transform matrix and choose the one with highest abs dot product. The sign just tells if the coordinate system is opposite (meaning the rotation should be reversed).

    In C++ and OpenGL style matrices it looks like this:

    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;
        }
    

    Where reper is class containing direct and invers transform matrix. the get_axis just peek inside direct matrix and return selected axis direction unit vector. The vector_mul is dot product and vector_ld just fills 3D vector with x,y,z coordinates.

    As I got also the global cube matrix which is not axis aligned to unit matrix (as it is rotated so the view looks like the image above) Then I need to do this axis matching against special vectors (initial view matrix values) In my case it is this:

    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;
        }
    

    Both functions return which axis is which x,y,z and if the direction is opposite (sx,sy,sz) in comparison to the unit transform matrix.

  4. Slice rotation

    This is the core of the puzzle. It simple turn of slice around axis. This is used to animate so the angle step is small (I use 9 degree) but the whole turn must be 90 degree total otherwise the Rubic Cube would break.

    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);
            }
        }
    

    Where reper::gpos_get returns matrix origin as 3D vector (point) and reper::gpos_set basically sets new matrix position. The vector_rotx(p0,p,a) rotates vector p around p0 and axis x by angle a. The +/- signs are only to match the rotations from reper class (I got difference somewhere). The reper::lrotx rotates reper around its local x axis for more info see the first link.

    As you can see I am using each matrix origin coordinates directly as topology to select the slice cubes.

Here you can try my demo: Win32+OpenGL Rubic Cube Demo

And Here animated gif of some turns:

[Edit1] I added simple solver to my RubiCube

To implement a solver I added surface planar color map (on the left ... the middle square is name and index of side I use) computed from my RubiCube internal representation. I also add internal commands que for the solver (axises and direction on the right):

Each commands is represented by 2 character strings:

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

And the map looks like this:

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

Where map[side][u][v] contains color of square on side s, row u and column v. I implemented simple 7 steps solution (like solving real cube by human):

  1. input state (not a step)
  2. White cross with Yellow middle (Yellow middle is facing front)
  3. White cross (White middle is facing front)
  4. White corners (white side is facing down)
  5. Middle layer (using first 3 commands)
  6. Top layer yellow cross (using 4th command)
  7. reordering cross so sides match (5th command) and reordering corners (6th command)
  8. orienting top layer corners to complete cube (7th command)

Solver is simple and operates on strings (unoptimized) so its a bit slow but anyway full solution takes just up to 50ms on my setup. You can try here the upgraded demo:

There may still be some undefined cases (due to bug or missed case in code) while solving. In such case the app hangs of coarse (did not implement watchdog yet). The keys are described in the text file included.

I did the solver lightweight (cca 300 lines of code) so the found solution is far from optimal. For example instead testing 4 corners I test only one and rotate the cube in loop causing unnecessary turns. Some of them are filtered out latter but the average human (my) solution is up to 200 turns and this solver return up to 300 turns instead (in worst case I found till now).

这篇关于四元数旋转不能正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆