为了能获取运行时的模型视图和投影矩阵以便计算手点击魔方时的接触面,故在设置opengl时不采用opengl自带的api,而采用托管矩阵的方式,对矩阵进行运算后再直接赋给opengl。
在onSurfaceChanged方法中:
//GLU.gluPerspective(gl, 45f, ratio, 2,12);
//改为托管矩阵运行
Matrix4f.gluPersective(45.0f,ratio,0.1f,100,AppConfig.gMatProject);
gl.glLoadMatrixf(AppConfig.gMatProject.asFloatBuffer());
在onDrawFrame方法中:
// gl.glTranslatef(0, 0, -3.0f);
Matrix4f.gluLookAt(mvEye,mvCenter,mvUp, AppConfig.gMatView);
gl.glLoadMatrixf(AppConfig.gMatView.asFloatBuffer());
AppConfig.gMatModel.setIdentity();
if (AppConfig.gbNeedPick && !touchInCubeSphere()) {
mAngleX += offsetX;
mAngleY += offsetY;
}
// gl.glRotatef(mAngle, 0, 1, 0);
// gl.glRotatef(mAngle*0.25f, 1, 0, 0);
//矩阵旋转
Matrix4f matRotX = new Matrix4f();
matRotX.setIdentity();
matRotX.rotX((float) (mAngleX * Math.PI / 180));
AppConfig.gMatModel.mul(matRotX);
Matrix4f matRotY = new Matrix4f();
matRotY.setIdentity();
matRotY.rotY((float) (mAngleY * Math.PI / 180));
AppConfig.gMatModel.mul(matRotY);
gl.glMultMatrixf(AppConfig.gMatModel.asFloatBuffer());
其中mAngleX和mAngleY是在GLSurfaceView中的onTouchEvent方法中根据手指在屏幕上的移动距离计算。
射线拾取原理
http://www.ophonesdn.com/article/show/164
射线拾取实现
http://vaero.blog.51cto.com/4350852/790620
当我们知道手接触到面时,就希望魔方能按照手指的滑动方向转动。
在该项目中将魔方分为9层:上、中1、下、左、中2、右、前、中3、后。
如图所示,当手指依次滑过6、7、8时,这三个方块同时是属于前层和上层,一般来说本次操作是希望上层旋转,而当使用射线拾取时,射线与前层距离最近的面时由6、7、8、15、16、17、24、25、26组成的面A;射线与上层距离最近的面是6、7、8组成的面B。射线到A和B面的距离应该是相等的(实际计算时还是有误差,误差在0.00001),这时可以计算A和B面的面积,去面积小的面所在层转动。
// 如果发生了相交
if (transformedRay.intersectTriangle(v0, v1, v2, location)) {
Log.d("GLWorld", "层" + layer.index + "与射线相交,距离屏幕:" + location.w);
if (!bFound) {
// 如果是初次检测到,需要存储射线原点与三角形交点的距离值
bFound = true;
closeDis = location.w;
nearstLayer = layer;
nearest[0] = v0;
nearest[1] = v1;
nearest[2] = v2;
} else {
// 如果之前已经检测到相交事件,则需要把新相交点与之前的相交数据相比较
// 最终保留离射线原点更近的(如误差则一定范围内就判断相交平面的面积)
if(Math.abs(closeDis-location.w)<0.0001){
//与平面距离近似相等则判断三角形面积
//比当前的大则说明面靠前
double area1 = calculateArea(nearest[0],nearest[1],nearest[2]);
double area2 = calculateArea(v0,v1,v2);
if (area2>area1) {
nearstLayer = layer;
nearest[0] = v0;
nearest[1] = v1;
nearest[2] = v2;
}
}
else if (closeDis > location.w) {
closeDis = location.w;
nearstLayer = layer;
nearest[0] = v0;
nearest[1] = v1;
nearest[2] = v2;
}
}
}
为了实现局部转动,可以将需要转动的顶点乘以旋转矩阵,当转动结束时更新当前各层的方块。
旋转矩阵:
public void animateTransform(M4 transform) {
mAnimateTransform = transform;
//累计旋转的角度
if (mTransform != null)
transform = mTransform.multiply(transform);
Iterator<GLVertex> iter = mVertexList.iterator();
while (iter.hasNext()) {
GLVertex vertex = iter.next();
//mWorld.transformVertex(vertex, transform);
vertex.update(mVertexBuffer, transform);
}
}
魔方在旋转前,预先将方块顺时针和逆时针将要选择到的位置进行设置,在旋转完毕时只需要按设置好的数组更新每层的方块。
/*
* 跟每一层方块位置进行编号(由上至下),旋转后坐标一定要写对,否则坐标保存的与看到的不同。(注意:这里的编号是位置编号不是方块编号)
* 0 1 2 2 5 8
* 3 4 5 ->顺时针选择90度->1 4 7
* 6 7 80 3 6
* */
static int[][] mLayerCWPermutations = {
// permutation for UP layer 最上层顺时针旋转90度后布局
{ 2, 5, 8, 1, 4, 7, 0, 3, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 },
// permutation for DOWN layer 最下层顺时针旋转90度后布局
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20, 23, 26, 19, 22, 25, 18, 21, 24 },
// permutation for LEFT layer 左侧旋转90度
{ 6, 1, 2, 15, 4, 5, 24, 7, 8, 3, 10, 11, 12, 13, 14, 21, 16, 17, 0, 19, 20, 9, 22, 23, 18, 25, 26 },
// permutation for RIGHT layer 右侧旋转90度
{ 0, 1, 8, 3, 4, 17, 6, 7, 26, 9, 10, 5, 12, 13, 14, 15, 16, 23, 18, 19, 2, 21, 22, 11, 24, 25, 20 },
// permutation for FRONT layer 前面旋转90度
{ 0, 1, 2, 3, 4, 5, 24, 15, 6, 9, 10, 11, 12, 13, 14, 25, 16, 7, 18, 19, 20, 21, 22, 23, 26, 17, 8 },
// permutation for BACK layer 后面旋转90度
{ 18, 9, 0, 3, 4, 5, 6, 7, 8, 19, 10, 1, 12, 13, 14, 15, 16, 17, 20, 11, 2, 21, 22, 23, 24, 25, 26 },
// permutation for MIDDLE layer (中间面绕X轴旋转90度)
{ 0, 7, 2, 3, 16, 5, 6, 25, 8, 9, 4, 11, 12, 13, 14, 15, 22, 17, 18, 1, 20, 21, 10, 23, 24, 19, 26 },
// permutation for EQUATOR layer (中间绕Y轴旋转90度)
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 17, 10, 13, 16, 9, 12, 15, 18, 19, 20, 21, 22, 23, 24, 25, 26 },
// permutation for SIDE layer (中间绕Z轴旋转90度)
{ 0, 1, 2, 21, 12, 3, 6, 7, 8, 9, 10, 11, 22, 13, 4, 15, 16, 17, 18, 19, 20, 23, 14, 5, 24, 25, 26 }
};
//如果旋转到位
mCurrentLayer.setAngle(mEndAngle);
mCurrentLayer.endAnimation();
mCurrentLayer = null;
layerID=-1;
//adjust mPermutation based on the completed layer rotation
int[] newPermutation = new int[27];
for (int i = 0; i < 27; i++) {
//更新各层的方块
//mCurrentLayerPermutation[i]相当于方块的在数组中的地址
newPermutation[i] = mPermutation[mCurrentLayerPermutation[i]];
}
mPermutation = newPermutation;
updateLayers();