在 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))));
我使用两个四元数进行旋转。每次用户选择一个 Action 时,应该发生一个轮换。这是代码:
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);
现在的问题是它在第一次旋转时效果很好。无论第一次轮换是什么。它运行良好,但对于下一次旋转,它似乎得到了错误的轴和度数。
例如,如果坐标系是
然后第一次逆时针 (CCW) 旋转 X 90 度产生
绕 Z 轴逆时针 90 度的第二次旋转产生
但我期待
我认为问题在于 resultVector(我使用的第二个向量从原点到视点)没有正确转换。有谁知道如何将世界坐标转换为对象坐标?有谁知道当物体旋转时我们如何确定物体坐标?
最佳答案
好吧,昨天我决定编写 Rubic Cube 拼图,因为我过去尝试过的任何拼图对我来说都非常不舒服,最后我有一些心情/时间自己编写代码。当我完成它时,这里是我的见解:
我不认为四元数是一个好的选择。相反,我更喜欢:
所以我最终得到了
3*3*3=27
的列表变换矩阵加上一个用于整个立方体旋转的附加矩阵。在起始状态下,所有子立方体都有单位旋转部分,原点设置为覆盖{ -1 , 0 ,+1 }
的所有组合。填充整个魔方(每个子立方体网格的大小为 1.0
并以 (0,0,0)
为中心)我的多维数据集在 C++ 代码中定义如下:
reper cube[27]; // reper is transform matrix
我希望控制和查看尽可能接近真实的东西。因此,旋转由鼠标通过单击目标子立方体(在
area0
或 area1
中)来控制,然后从鼠标拖动的方向决定哪个轴旋转以及向哪个方向旋转。从起始位置没有问题(因为即使您的代码也适用于此)。问题从下一次旋转开始(尤其是在改变旋转轴时),因为局部坐标系已经改变。全局 View 旋转也是如此,因为它会弄乱所有这些。
我想出了一个模糊的解决方案,我首先从每个坐标系匹配轴。要检测哪个轴是哪个轴,我只需对查询方向与变换矩阵的所有轴进行点积,然后选择 abs 点积最高的那个轴。该符号仅说明坐标系是否相反(意味着应该反转旋转)。
在 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 矢量坐标。因为我也得到了全局立方体矩阵,它不是与单位矩阵对齐的(因为它被旋转,所以 View 看起来像上图)然后我需要对特殊向量(初始 View 矩阵值)进行这个轴匹配在我的情况下就是这个:
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)。 这是拼图的核心。它简单地绕轴旋转切片。这用于动画,因此角度步长很小(我使用 9 度),但整个转弯必须总共 90 度,否则魔方会破裂。
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
旋转 reper
围绕其本地x
轴有关更多信息,请参阅第一个链接。如您所见,我直接使用每个矩阵原点坐标作为拓扑来选择切片立方体。
在这里你可以试试我的演示: Win32+OpenGL Rubic Cube Demo
这里是一些转弯的动画 gif:
[Edit1] 我在我的 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
map 如下所示:
int map[6][3][3];
哪里
map[side][u][v]
包含侧面正方形的颜色 s
, 行 u
和专栏v
.我实现了简单 7 steps solution (就像人类解决真正的立方体一样):求解器很简单,对字符串进行操作(未优化),所以它有点慢,但无论如何,完整的解决方案在我的设置中最多需要 50 毫秒。您可以在这里尝试升级的演示:
解决时可能仍然存在一些未定义的情况(由于代码中的错误或遗漏情况)。在这种情况下,应用程序会很粗糙(尚未实现看门狗)。 key 在包含的文本文件中进行了描述。
我做了轻量级的求解器(大约 300 行代码),所以找到的解决方案远非最佳。例如,代替测试 4 个角,我只测试一个角并循环旋转立方体,导致不必要的转弯。其中一些后来被过滤掉了,但人类(我的)解决方案的平均次数高达 200 圈,而这个求解器却返回了 300 圈(在最坏的情况下,我直到现在才发现)。
关于android - 四元数旋转不能正常工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11467919/