Android OpenGL射线拾取&手势旋转

         实现这个工程,主要参考了《OPhone 3D开发之射线拾取》一文。这次又是在家写的,没网T^T,所以需要的还劳烦自己Google吧。

         一是通过射线拾取监听正方体各面的点击事件,二是使用绕任意轴旋转实现正方体一直按手势方向进行旋转(即无论正方体如何翻转,旋转方向一直跟随手势方向)。

opengl.jpg

图1 样例效果图
         模拟器上旋转好卡T^T。
 
一、我的 OpenGL 概念
1 )OpenGL 坐标系
         OpenGL为右手笛卡尔坐标系统。这么描述吧:高抬右手,大拇指向右、食指向上、中指弯90°。大拇指方向为X正轴方向(从左到右),食指方向为Y正轴方向(从下到上),中指呢就是Z正轴方向(从里到外)。
         绕轴旋转,遵循右手法则。例如右手握住Y轴,大拇指指向Y正轴方向,四指弯曲方向即为绕轴旋转方向。
         (以上手头文档发现都没描述==,记忆里是这样的,有误还请指正!)
 
         OpenGL中从三维场景到屏幕图形要经历如下所示的变换过程:模型坐标->世界坐标->观察坐标->投影坐标->设备坐标。
         其中四种坐标经常要在程序中用到:物体坐标(也叫模型坐标、局部坐标),世界坐标,眼坐标(也叫观察坐标)和设备坐标。
         世界坐标即刚开始描述的坐标系统,是用来描述OpenGL中的场景。
 
         OpenGL中的坐标变换都是通过矩阵运算完成的,与图形学课本描述一致。由于样例工做了一些自己的矩阵变换,有些概念最好过目一下。小弟也就过目了下^^,了解矩阵为啥都是4X4的,以及简单的缩放矩阵、平移矩阵什么的就成。在这基础上写绕任意单位轴旋转任意角度的矩阵时,就不至于迷茫了。
 
2 )OpenGL 库函数
         貌似有核心库(gl)、实用库(glu)、辅助库(aux)、实用工具库(glut)、窗口库(glx、agl、wgl)和扩展函数库等==。也就不详细说了(事实上是,我没用过啊T^T)。
         样例工程很简单的,知道下Java层的GL10和GLU就成了-_-!
 
3 )OpenGL 的学习
         空间几何:和我一样已经生疏了的同学,还请搜“空间解析几何与向量代数”。
         线性代数:这门课的书,都卖了没啊?仍上学的不算==。
         图形学:这个不懂啊,总之学OpenGL要看这个T^T。
         OpenGL:OpenGL编程指南^^,就从这开始吧。瞄过,还不错的样子。
 
         小弟路走岔了,不是从事OpenGL的,上述也就是自己瞎磨叽的。不过应该是这样的吧,应该错不了==。
 
二、射线拾取概念
1 )什么是拾取
         根据2D屏幕坐标来选取3D空间中图元的操作,就是拾取。
 
2 )拾取射线
         搬个拾取原理图过来^^。

3d_pick.jpg

图2.2 拾取原理图

         z = 0处为视锥体近剪裁面,z = 1处为远剪裁面。拾取射线,就是由触摸位置在近剪裁面上的位置P0,以及在远剪裁面上的位置P1所组成的,其中,P0为射线原点,射线由P0发射指向P1。概括点就是,2D平面的一个点映射至3D空间为一射线。

         好吧,这严重涉及了我们工程的目标之一:点击立方体某一面触发某事件。样例工程做的是渲染选中的三角以及提示当前选中了哪面。
 
三、主要实现内容
1 )正方体类:Cube.java
         绘制正方体所需的相关信息,以及射线与其的碰撞检测方法(及射线与正方形各个小三角形的碰撞检测)、返回外接圆信息的方法(用于快速排除外切圆区域外的触摸事件)。
 
 
  
  1. public class Cube { 
  2.  
  3.     public static final int VERTEX_BUFFER = 0
  4.     public static final int TEXTURE_BUFFER = 1
  5.  
  6.     private float one = 1.0f; 
  7.  
  8.     // 立方体顶点坐标 
  9.     private float[] vertices = new float[] { -one, -one, one, one, -one, one, 
  10.             one, one, one, -one, one, one, -one, -one, -one, -one, one, -one, 
  11.             one, one, -one, one, -one, -one, -one, one, -one, -one, one, one, 
  12.             one, one, one, one, one, -one, -one, -one, -one, one, -one, -one, 
  13.             one, -one, one, -one, -one, one, one, -one, -one, one, one, -one, 
  14.             one, one, one, one, -one, one, -one, -one, -one, -one, -one, one, 
  15.             -one, one, one, -one, one, -one }; 
  16.  
  17.     // 立方体纹理坐标 
  18.     private float[] texCoords = new float[] { one, 0000, one, one, one, 
  19.             000, one, one, one, one, 0, one, one, one, 0000, one, 0
  20.             one, one, one, one, 000000, one, one, one, one, 0, one, 
  21.             0000, one, one, one }; 
  22.  
  23.     // 三角形描述顺序 
  24.     private byte[] indices = new byte[] { 01324576891110
  25.             121315141617191820212322 }; 
  26.  
  27.     // 触碰的立方体某一面的标记(0-5) 
  28.     public int surface = -1
  29.  
  30.     // 获得坐标的缓存对象 
  31.     public FloatBuffer getCoordinate(int coord_id) { 
  32.         switch (coord_id) { 
  33.         case VERTEX_BUFFER: 
  34.             return getDirectBuffer(vertices); 
  35.         case TEXTURE_BUFFER: 
  36.             return getDirectBuffer(texCoords); 
  37.         default
  38.             throw new IllegalArgumentException(); 
  39.         } 
  40.     } 
  41.  
  42.     // 获得三角形描述顺序 
  43.     public ByteBuffer getIndices() { 
  44.         return ByteBuffer.wrap(indices); 
  45.     } 
  46.  
  47.     public FloatBuffer getDirectBuffer(float[] buffer) { 
  48.         ByteBuffer bb = ByteBuffer.allocateDirect(buffer.length * 4); 
  49.         bb.order(ByteOrder.nativeOrder()); 
  50.         FloatBuffer directBuffer = bb.asFloatBuffer(); 
  51.         directBuffer.put(buffer); 
  52.         directBuffer.position(0); 
  53.         return directBuffer; 
  54.     } 
  55.  
  56.     // 返回立方体外切圆的中心点 
  57.     public Vector3f getSphereCenter() { 
  58.         return new Vector3f(000); 
  59.     } 
  60.  
  61.     // 返回立方体外切圆的半径(√3) 
  62.     public float getSphereRadius() { 
  63.         return 1.732051f; 
  64.     } 
  65.  
  66.     private static Vector4f location = new Vector4f(); 
  67.  
  68.     /** 
  69.      * 射线与模型的精确碰撞检测 
  70.      *  
  71.      * @param ray 
  72.      *            - 转换到模型空间中的射线 
  73.      * @param trianglePosOut 
  74.      *            - 返回的拾取后的三角形顶点位置 
  75.      * @return 如果相交,返回true 
  76.      */ 
  77.     public boolean intersect(Ray ray, Vector3f[] trianglePosOut) { 
  78.         boolean bFound = false
  79.         // 存储着射线原点与三角形相交点的距离 
  80.         // 我们最后仅仅保留距离最近的那一个 
  81.         float closeDis = 0.0f; 
  82.  
  83.         Vector3f v0, v1, v2; 
  84.  
  85.         // 立方体6个面 
  86.         for (int i = 0; i < 6; i++) { 
  87.  
  88.             // 每个面两个三角形 
  89.             for (int j = 0; j < 2; j++) { 
  90.                 if (0 == j) { 
  91.                     v0 = getVector3f(indices[i * 4 + j]); 
  92.                     v1 = getVector3f(indices[i * 4 + j + 1]); 
  93.                     v2 = getVector3f(indices[i * 4 + j + 2]); 
  94.                 } else { 
  95.                     // 第二个三角形时,换下顺序,不然会渲染到立方体内部 
  96.                     v0 = getVector3f(indices[i * 4 + j]); 
  97.                     v1 = getVector3f(indices[i * 4 + j + 2]); 
  98.                     v2 = getVector3f(indices[i * 4 + j + 1]); 
  99.                 } 
  100.  
  101.                 // 进行射线和三角行的碰撞检测 
  102.                 if (ray.intersectTriangle(v0, v1, v2, location)) { 
  103.                     // 如果发生了相交 
  104.                     if (!bFound) { 
  105.                         // 如果是初次检测到,需要存储射线原点与三角形交点的距离值 
  106.                         bFound = true
  107.                         closeDis = location.w; 
  108.                         trianglePosOut[0].set(v0); 
  109.                         trianglePosOut[1].set(v1); 
  110.                         trianglePosOut[2].set(v2); 
  111.                         surface = i; 
  112.                     } else { 
  113.                         // 如果之前已经检测到相交事件,则需要把新相交点与之前的相交数据相比较 
  114.                         // 最终保留离射线原点更近的 
  115.                         if (closeDis > location.w) { 
  116.                             closeDis = location.w; 
  117.                             trianglePosOut[0].set(v0); 
  118.                             trianglePosOut[1].set(v1); 
  119.                             trianglePosOut[2].set(v2); 
  120.                             surface = i; 
  121.                         } 
  122.                     } 
  123.                 } 
  124.             } 
  125.         } 
  126.         return bFound; 
  127.     } 
  128.  
  129.     private Vector3f getVector3f(int start) { 
  130.         return new Vector3f(vertices[3 * start], vertices[3 * start + 1], 
  131.                 vertices[3 * start + 2]); 
  132.     } 
 
2 )GLSurfaceView :MyGLSurfaceView.java
         专门用来实现OpenGL渲染的SurfaceView,在触屏事件里计算出绕轴的单位向量。

 
  
  1. public class MyGLSurfaceView extends GLSurfaceView { 
  2.  
  3.     // private final float TOUCH_SCALE_FACTOR = 180.0f / 480; 
  4.  
  5.     /** 
  6.      * 具体实现的渲染器 
  7.      */ 
  8.     private RayPickRenderer mRenderer; 
  9.     /** 
  10.      * 记录上次触屏位置的坐标 
  11.      */ 
  12.     private float mPreviousX, mPreviousY; 
  13.  
  14.     public MyGLSurfaceView(Context context, 
  15.             OnSurfacePickedListener onSurfacePickedListener) { 
  16.         super(context); 
  17.         // 设置渲染器 
  18.         mRenderer = new RayPickRenderer(context); 
  19.  
  20.         // 透视上一个View 
  21.         setZOrderOnTop(true); 
  22.         setEGLConfigChooser(8888160); 
  23.         // 透视上一个Activity 
  24.         getHolder().setFormat(PixelFormat.TRANSLUCENT); 
  25.  
  26.         setRenderer(mRenderer); 
  27.         // 设置渲染模式为主动渲染 
  28.         setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); 
  29.  
  30.         mRenderer.setOnSurfacePickedListener(onSurfacePickedListener); 
  31.     } 
  32.  
  33.     public void onPause() { 
  34.         super.onPause(); 
  35.     } 
  36.  
  37.     public void onResume() { 
  38.         super.onResume(); 
  39.     } 
  40.  
  41.     /** 
  42.      * 响应触屏事件 
  43.      */ 
  44.     @Override 
  45.     public boolean onTouchEvent(MotionEvent e) { 
  46.         float x = e.getX(); 
  47.         float y = e.getY(); 
  48.         AppConfig.setTouchPosition(x, y); 
  49.         switch (e.getAction()) { 
  50.         case MotionEvent.ACTION_MOVE: 
  51.  
  52.             // 经过中心点的手势方向逆时针旋转90°后的坐标 
  53.             float dx = y - mPreviousY; 
  54.             float dy = x - mPreviousX; 
  55.             // 手势距离 
  56.             float d = (float) (Math.sqrt(dx * dx + dy * dy)); 
  57.             // 旋转轴单位向量的x,y值(z=0) 
  58.             mRenderer.mfAngleX = dx; 
  59.             mRenderer.mfAngleY = dy; 
  60.             // 手势距离 
  61.             mRenderer.gesDistance = d; 
  62.  
  63.             // float dx = x - mPreviousX; 
  64.             // float dy = y - mPreviousY; 
  65.             // mRenderer.mfAngleY += dx * TOUCH_SCALE_FACTOR; 
  66.             // mRenderer.mfAngleX += dy * TOUCH_SCALE_FACTOR; 
  67.  
  68.             // PickFactory.update(x, y); 
  69.             AppConfig.gbNeedPick = false
  70.             break
  71.         case MotionEvent.ACTION_DOWN: 
  72.             AppConfig.gbNeedPick = false
  73.             break
  74.         case MotionEvent.ACTION_UP: 
  75.             AppConfig.gbNeedPick = true
  76.             break
  77.         case MotionEvent.ACTION_CANCEL: 
  78.             AppConfig.gbNeedPick = false
  79.             break
  80.         } 
  81.         mPreviousX = x; 
  82.         mPreviousY = y; 
  83.         return true
  84.     } 
        超过8W字符T^T,继续->