天天看點

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,如需轉載請自行聯系原作者

繼續閱讀