天天看点

Android OpenGL射线拾取&手势旋转(二)

<b>3</b><b>)Renderer</b><b>:RayPickRenderer.java</b>

         OpenGL渲染器,比较多的东西都在这里面了。

public class RayPickRenderer implements Renderer { 

    private Context mContext; 

    private Cube cube; 

    int texture = -1; 

    public float mfAngleX = 0.0f; 

    public float mfAngleY = 0.0f; 

    public float gesDistance = 0.0f; 

    // 观察者、中心和上方 

    private Vector3f mvEye = new Vector3f(0, 0, 7f), mvCenter = new Vector3f(0, 

            0, 0), mvUp = new Vector3f(0, 1, 0); 

    private OnSurfacePickedListener onSurfacePickedListener; 

    public RayPickRenderer(Context context) { 

        mContext = context; 

        cube = new Cube(); 

    } 

    /** 

     * 逐帧渲染 

     */ 

    @Override 

    public void onDrawFrame(GL10 gl) { 

        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存 

        gl.glLoadIdentity(); // 重置当前的模型观察矩阵 

        // 紧接着设置模型视图矩阵 

        setUpCamera(gl); 

        gl.glPushMatrix(); 

        { 

            // 渲染物体 

            drawModel(gl); 

        } 

        gl.glPopMatrix(); 

        // gl.glPushMatrix(); 

        // { 

        // // 渲染射线 

        // PickFactory.getPickRay().draw(gl); 

        // } 

        // gl.glPopMatrix(); 

            // 渲染选中的三角形 

            drawPickedTriangle(gl); 

        updatePick(); 

     * 设置相机矩阵 

     *  

     * @param gl 

    private void setUpCamera(GL10 gl) { 

        // 设置模型视图矩阵 

        gl.glMatrixMode(GL10.GL_MODELVIEW); 

        gl.glLoadIdentity(); 

        // GLU.gluLookAt(gl, mfEyeX, mfEyeY, mfEyeZ, mfCenterX, mfCenterY, 

        // mfCenterZ, 0, 1, 0);//系统提供 

        Matrix4f.gluLookAt(mvEye, mvCenter, mvUp, AppConfig.gMatView); 

        gl.glLoadMatrixf(AppConfig.gMatView.asFloatBuffer()); 

    // private Matrix4f matRotX = new Matrix4f(); 

    // private Matrix4f matRotY = new Matrix4f(); 

    private Matrix4f matRot = new Matrix4f(); 

    private Vector3f point; 

     * 渲染模型 

    private void drawModel(GL10 gl) { 

        // 首先对模型进行变换 

        // -------使用系统函数进行变换 

        // gl.glRotatef(mfAngleX, 1, 0, 0);//绕X轴旋转 

        // gl.glRotatef(mfAngleY, 0, 1, 0);//绕Y轴旋转 

        // -------托管方式进行变换 

        // matRotX.setIdentity(); 

        // matRotY.setIdentity(); 

        // matRotX.rotX((float) (mfAngleX * Math.PI / 180)); 

        // matRotY.rotY((float) (mfAngleY * Math.PI / 180)); 

        // AppConfig.gMatModel.set(matRotX); 

        // AppConfig.gMatModel.mul(matRotY); 

        /* 以下方式完全按照手势方向旋转 */ 

        matRot.setIdentity(); 

        // 世界坐标系的向量点 

        point = new Vector3f(mfAngleX, mfAngleY, 0); 

        try { 

            // 转换到模型内部的点,先要求逆 

            matInvertModel.set(AppConfig.gMatModel); 

            matInvertModel.invert(); 

            matInvertModel.transform(point, point); 

            float d = Vector3f.distance(new Vector3f(), point); 

            // 再减少误差 

            if (Math.abs(d - gesDistance) &lt;= 1E-4) { 

                // 绕这个单位向量旋转(由于误差可能会产生缩放而使得模型消失不见) 

                matRot.glRotatef((float) (gesDistance * Math.PI / 180), point.x 

                        / d, point.y / d, point.z / d); 

                // 旋转后在原基础上再转 

                if (0 != gesDistance) { 

                    AppConfig.gMatModel.mul(matRot); 

                } 

            } 

        } catch (Exception e) { 

            // 由于四舍五入求逆矩阵失败 

        gesDistance = 0; 

        gl.glMultMatrixf(AppConfig.gMatModel.asFloatBuffer()); 

        // 设置默认颜色 

        gl.glColor4f(1.0f, 1.0f, 1.0f, 0.0f); 

        gl.glBindTexture(GL10.GL_TEXTURE_2D, texture); 

        gl.glEnable(GL10.GL_TEXTURE_2D); 

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); 

        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); 

        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, 

                cube.getCoordinate(Cube.VERTEX_BUFFER)); 

        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, 

                cube.getCoordinate(Cube.TEXTURE_BUFFER)); 

        gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 24, GL10.GL_UNSIGNED_BYTE, 

                cube.getIndices()); 

        gl.glDisable(GL10.GL_TEXTURE_2D); 

        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); 

        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); 

        // 渲染坐标系 

        drawCoordinateSystem(gl); 

    private Vector3f transformedSphereCenter = new Vector3f(); 

    private Ray transformedRay = new Ray(); 

    private Matrix4f matInvertModel = new Matrix4f(); 

    private Vector3f[] mpTriangle = { new Vector3f(), new Vector3f(), 

            new Vector3f() }; 

    private FloatBuffer mBufPickedTriangle = IBufferFactory 

            .newFloatBuffer(3 * 3); 

     * 更新拾取事件 

    private void updatePick() { 

        if (!AppConfig.gbNeedPick) { 

            return; 

        AppConfig.gbNeedPick = false; 

        // 更新最新的拾取射线 

        PickFactory.update(AppConfig.gScreenX, AppConfig.gScreenY); 

        // 获得最新的拾取射线 

        Ray ray = PickFactory.getPickRay(); 

        // 首先把模型的绑定球通过模型矩阵,由模型局部空间变换到世界空间 

        AppConfig.gMatModel.transform(cube.getSphereCenter(), 

                transformedSphereCenter); 

        // 触碰的立方体面的标记为无 

        cube.surface = -1; 

        // 首先检测拾取射线是否与模型绑定球发生相交 

        // 这个检测很快,可以快速排除不必要的精确相交检测 

        if (ray.intersectSphere(transformedSphereCenter, cube.getSphereRadius())) { 

            // 如果射线与绑定球发生相交,那么就需要进行精确的三角面级别的相交检测 

            // 由于我们的模型渲染数据,均是在模型局部坐标系中 

            // 而拾取射线是在世界坐标系中 

            // 因此需要把射线转换到模型坐标系中 

            // 这里首先计算模型矩阵的逆矩阵 

            // 把射线变换到模型坐标系中,把结果存储到transformedRay中 

            ray.transform(matInvertModel, transformedRay); 

            // 将射线与模型做精确相交检测 

            if (cube.intersect(transformedRay, mpTriangle)) { 

                // 如果找到了相交的最近的三角形 

                AppConfig.gbTrianglePicked = true; 

                // 触碰了哪一个面 

                Log.i("触碰的立方体面", "=标记=" + cube.surface); 

                // 回调 

                if (null != onSurfacePickedListener) { 

                    onSurfacePickedListener.onSurfacePicked(cube.surface); 

                // 填充数据到被选取三角形的渲染缓存中 

                mBufPickedTriangle.clear(); 

                for (int i = 0; i &lt; 3; i++) { 

                    IBufferFactory 

                            .fillBuffer(mBufPickedTriangle, mpTriangle[i]); 

                    // Log.i("点" + i, mpTriangle[i].x + "\t" + mpTriangle[i].y 

                    // + "\t" + mpTriangle[i].z); 

                mBufPickedTriangle.position(0); 

        } else { 

            AppConfig.gbTrianglePicked = false; 

     * 渲染选中的三角形 

    private void drawPickedTriangle(GL10 gl) { 

        if (!AppConfig.gbTrianglePicked) { 

        // 由于返回的拾取三角形数据是出于模型坐标系中 

        // 因此需要经过模型变换,将它们变换到世界坐标系中进行渲染 

        // 设置模型变换矩阵 

        // 设置三角形颜色,alpha为0.7 

        gl.glColor4f(1.0f, 0.0f, 0.0f, 0.7f); 

        // 开启Blend混合模式 

        gl.glEnable(GL10.GL_BLEND); 

        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); 

        // 禁用无关属性,仅仅使用纯色填充 

        gl.glDisable(GL10.GL_DEPTH_TEST); 

        // 开始绑定渲染顶点数据 

        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufPickedTriangle); 

        // 提交渲染 

        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3); 

        // 重置相关属性 

        gl.glEnable(GL10.GL_DEPTH_TEST); 

        gl.glDisable(GL10.GL_BLEND); 

     * 渲染坐标系 

    private void drawCoordinateSystem(GL10 gl) { 

        // 暂时禁用深度测试 

        // 设置点和线的宽度 

        gl.glLineWidth(2.0f); 

        // 仅仅启用顶点数据 

        FloatBuffer fb = IBufferFactory.newFloatBuffer(3 * 2); 

        fb.put(new float[] { 0, 0, 0, 1.4f, 0, 0 }); 

        fb.position(0); 

        // 渲染X轴 

        gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);// 设置红色 

        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb); 

        gl.glDrawArrays(GL10.GL_LINES, 0, 2); 

        fb.clear(); 

        fb.put(new float[] { 0, 0, 0, 0, 1.4f, 0 }); 

        // 渲染Y轴 

        gl.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);// 设置绿色 

        fb.put(new float[] { 0, 0, 0, 0, 0, 1.4f }); 

        // 渲染Z轴 

        gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);// 设置蓝色 

        // 重置 

        gl.glLineWidth(1.0f); 

     * 创建绘图表面时调用 

    public void onSurfaceCreated(GL10 gl, EGLConfig config) { 

        // 全局性设置 

        gl.glEnable(GL10.GL_DITHER); 

        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); 

        // 设置清屏背景颜色 

        // gl.glClearColor(0, 0, 0, 0); 

        gl.glClearColor(0.5f, 0.5f, 0.5f, 1); 

        // 设置着色模型为平滑着色 

        gl.glShadeModel(GL10.GL_SMOOTH); 

        // 启用背面剪裁 

        gl.glEnable(GL10.GL_CULL_FACE); 

        gl.glCullFace(GL10.GL_BACK); 

        // 启用深度测试 

        // 禁用光照和混合 

        gl.glDisable(GL10.GL_LIGHTING); 

        loadTexture(gl); 

        AppConfig.gMatModel.setIdentity(); 

     * 当绘图表面尺寸发生改变时调用 

    public void onSurfaceChanged(GL10 gl, int width, int height) { 

        // 设置视口 

        gl.glViewport(0, 0, width, height); 

        AppConfig.gpViewport[0] = 0; 

        AppConfig.gpViewport[1] = 0; 

        AppConfig.gpViewport[2] = width; 

        AppConfig.gpViewport[3] = height; 

        // 设置投影矩阵 

        float ratio = (float) width / height;// 屏幕宽高比 

        gl.glMatrixMode(GL10.GL_PROJECTION); 

        // GLU.gluPerspective(gl, 45.0f, ratio, 1, 5000);系统提供 

        Matrix4f.gluPersective(45.0f, ratio, 1, 10, AppConfig.gMatProject); 

        gl.glLoadMatrixf(AppConfig.gMatProject.asFloatBuffer()); 

        AppConfig.gMatProject.fillFloatArray(AppConfig.gpMatrixProjectArray); 

        // 每次修改完GL_PROJECTION后,最好将当前矩阵模型设置回GL_MODELVIEW 

    private void loadTexture(GL10 gl) { 

        // 启用纹理映射 

        gl.glClearDepthf(1.0f); 

        // 允许2D贴图,纹理 

            IntBuffer intBuffer = IntBuffer.allocate(1); 

            // 创建纹理 

            gl.glGenTextures(1, intBuffer); 

            texture = intBuffer.get(); 

            // 设置要使用的纹理 

            gl.glBindTexture(GL10.GL_TEXTURE_2D, texture); 

            // 打开二进制流 

            InputStream is = mContext.getResources().openRawResource( 

                    R.drawable.snow_leopard); 

            Bitmap mBitmap = BitmapFactory.decodeStream(is); 

            // Log.i("宽度|高度", mBitmap.getWidth() + "|" + mBitmap.getHeight()); 

            // 生成纹理 

            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0); 

            // 线形滤波 

            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, 

                    GL10.GL_LINEAR); 

            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, 

            is.close(); 

        } catch (IOException e) { 

            // TODO Auto-generated catch block 

            e.printStackTrace(); 

    public void setOnSurfacePickedListener( 

            OnSurfacePickedListener onSurfacePickedListener) { 

        this.onSurfacePickedListener = onSurfacePickedListener; 

<b></b>

<b>4</b><b>)主Activity</b><b>:RayPickActivity</b>

         继承各面点击的监听接口,提示了个Toast就OK了。很简单,当时都没注释。

public class RayPickActivity extends Activity implements 

        OnSurfacePickedListener { 

    private GLSurfaceView mGLSurfaceView; 

    public void onCreate(Bundle savedInstanceState) { 

        super.onCreate(savedInstanceState); 

        mGLSurfaceView = new MyGLSurfaceView(this, this); 

        mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); 

        setContentView(mGLSurfaceView); 

        mGLSurfaceView.requestFocus(); 

        mGLSurfaceView.setFocusableInTouchMode(true); 

    protected void onResume() { 

        super.onResume(); 

        mGLSurfaceView.onResume(); 

    protected void onPause() { 

        super.onPause(); 

        mGLSurfaceView.onPause(); 

    private Handler myHandler = new Handler() { 

        @Override 

        public void handleMessage(Message msg) { 

            Toast.makeText(RayPickActivity.this, "选中了" + msg.what + "面", 

                    Toast.LENGTH_SHORT).show(); 

    }; 

    public void onSurfacePicked(int which) { 

        myHandler.sendEmptyMessage(which); 

<b>5</b><b>)Matrix4f</b><b>内的glRotatef(…)</b><b>方法</b>

         公式如下图,其中其中C为cosθ,S为sinθ,A为单位化的旋转轴。

<a target="_blank" href="http://blog.51cto.com/attachment/201202/095112297.jpg"></a>

         但这是左手坐标系下的公式,之前有改过右手坐标系的但不能实现效果。其他几个绕X、Y、Z轴的也是这样。

         这应该是因为OpenGL坐标系原点在左下角,而窗口坐标系的原点为右上角,程序在Y轴上做了修正的关系导致的。这次可以高抬左手,大拇指向右、食指向下、弯曲中指90°,是不是各正轴都对应上了呢^^。

/** 

 * 绕任意单位轴旋转任意角度的矩阵 

 */ 

public final void glRotatef(float angle, float x, float y, float z) { 

    float sinAngle, cosAngle; 

    sinAngle = (float) Math.sin((double) angle); 

    cosAngle = (float) Math.cos((double) angle); 

    this.m00 = cosAngle + (1 - cosAngle) * x * x; 

    this.m01 = (1 - cosAngle) * x * y - sinAngle * z; 

    this.m02 = (1 - cosAngle) * x * z + sinAngle * y; 

    this.m03 = 0; 

    this.m10 = (1 - cosAngle) * x * y + sinAngle * z; 

    this.m11 = cosAngle + (1 - cosAngle) * y * y; 

    this.m12 = (1 - cosAngle) * y * z - sinAngle * x; 

    this.m13 = 0; 

    this.m20 = (1 - cosAngle) * x * z - sinAngle * y; 

    this.m21 = (1 - cosAngle) * y * z + sinAngle * x; 

    this.m22 = cosAngle + (1 - cosAngle) * z * z; 

    this.m23 = 0; 

    this.m30 = 0; 

    this.m31 = 0; 

    this.m32 = 0; 

    this.m33 = 1; 

<b>三、后记</b>

         小弟那时做这个,貌似是为了做个3D魔方啊。然后无聊可以在那转啊转啊的,不过愿望很美好……总之,我没做下去,忘记当时又忙活啥去了T^T。

         现在也没这个激情继续做这个呢,希望这篇文档能给那些有同样想法的人给予一定的帮助吧,加油^^。

<a href="http://down.51cto.com/data/2359933" target="_blank">附件:http://down.51cto.com/data/2359933</a>

     本文转自winorlose2000 51CTO博客,原文链接:http://blog.51cto.com/vaero/790637,如需转载请自行联系原作者

继续阅读