vuforia android 教程(4) 添加手勢識別 手勢操作 手勢觸控 vofuria模型 旋轉 縮放 放大 縮小

系列教程:

vuforia android 教程(1)
http://www.lxweimin.com/p/37b158175b04
vuforia android 教程(2)
http://www.lxweimin.com/p/2d70974857b8
vuforia android 教程(3)
http://www.lxweimin.com/p/ff33aa2ffde5

在上一篇文章中,我們了解了如何替換替換vofuria模型

那么接下來,我們來給 vuforia 模型 添加 手勢操作 旋轉 縮放

1. Android OpenGL ES 原理

1.1 Coordinate System坐標系

OpenGL 使用了右手坐標系統,右手坐標系判斷方法:在空間直角坐標系中,讓右手拇指指向x軸的正方向,食指指向y軸的正方向,如果中指能指向z軸的正方向,則稱這個坐標系為右手直角坐標系。

image

1.2 Translate 平移變換

方法 public abstract void glTranslatef (float x, float y, float z) 用于坐標平移變換。

image

1.3 Rotate 旋轉

方法 public abstract void glRotatef(float angle, float x, float y, float z) 用來實現選擇坐標變換,單位為角度。(x,y,z)定義旋轉的參照矢量方向。多次旋轉的順序非常重要。

image

1.4 Scale(縮放)

方法 public abstract void glScalef (float x, float y, float z)用于縮放變換。

下圖為使用 gl.glScalef(2f, 2f, 2f) 變換后的基本,相當于把每個坐標值都乘以2.

image

1.5 組合

Translate & Rotate (平移和旋轉組合變換)

在對 Mesh(網格,構成三維形體的基本單位)同時進行平移和選擇變換時,坐標變換的順序也直接影響最終的結果。

比如:先平移后旋轉,旋轉的中心為平移后的坐標。

image

先選擇后平移: 平移在則相對于旋轉后的坐標系:

image

一個基本原則是,坐標變換都是相對于變換的 Mesh 本身的坐標系而進行的。

Translate & Scale(平移和縮放組合變換)

同樣當需要平移和縮放時,變換的順序也會影響最終結果。

比如先平移后縮放:

gl.glTranslatef(2, 0, 0);
gl.glScalef(0.5f, 0.5f, 0.5f);  
image

如果調換一下順序:

gl.glScalef(0.5f, 0.5f, 0.5f);
gl.glTranslatef(2, 0, 0);  

結果就有所不同:

image

2.實際操作

2.1 單點旋轉

首先我們在以前的項目下 ,打開 ImageTargets.java ,在activity 中重寫onTouchEvent(MotionEvent event)方法.
千萬記住, 在opengl es 里面 ,是 xyz 坐標系 ,所以我們需要通過x軸 和 y軸進行旋轉 , 當我們需要模型左右旋轉的時候,就需要沿y軸進行旋轉,當我們需要模型上下旋轉的時候就需要沿x軸進行旋轉.

 int pointerCount = event.getPointerCount();

        int action = event.getAction();

        y = event.getY();
        x = event.getX();

        // 單點觸控的情況主要控制模型的旋轉

        if (pointerCount == 1) {

            switch (action) {

                case MotionEvent.ACTION_DOWN:

                    System.out.println("ACTION_DOWN pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_UP:

                    System.out.println("ACTION_UP pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_MOVE:

                    System.out.println("ACTION_MOVE pointerCount=" + pointerCount);

                    float dy = y - mPreviousY;//計算觸控筆Y位移
                    float dx = x - mPreviousX;//計算觸控筆X位移
                    mRenderer.setmAngleX(mRenderer.getmAngleX() + dy * TOUCH_SCALE_FACTOR);//設置沿x軸旋轉角度
                    mRenderer.setmAngleY(mRenderer.getmAngleY() + dx * TOUCH_SCALE_FACTOR);//設置沿y軸旋轉角度
                    break;

            }

        }

2.2 雙點縮放

當有兩個點的時候,就是縮放,判斷兩個點之間距離越大, 就放大, 距離越小就縮小.

  if (pointerCount == 2) {

            switch (action & MotionEvent.ACTION_MASK) {

                case MotionEvent.ACTION_DOWN:

                    System.out.println("ACTION_DOWN pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_POINTER_DOWN:

                    oldDist = (float) Math.sqrt((event.getX(0) - event.getX(1)) * (event.getX(0) - event.getX(1)) + (event.getY(0) - event.getY(1)) * (event.getY(0) - event.getY(1)));

                    System.out.println("ACTION_UP pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_MOVE:

                    System.out.println("ACTION_MOVE pointerCount=" + pointerCount);

                    float newDist = (float) Math.sqrt((event.getX(0) - event.getX(1)) * (event.getX(0) - event.getX(1)) + (event.getY(0) - event.getY(1)) * (event.getY(0) - event.getY(1)));

                    float scale = newDist / oldDist;

                    if (scale >= 1.5f) {

                        scale = 1.5f;

                    } else if (scale <= 0.2f) {

                        scale = 0.2f;

                    }

                    mRenderer.setScale(scale);//調用本地方法傳值

                    break;

            }

        }

2.3 通過onTouchEvent()計算的值在Renderer里面進行實際旋轉和縮放

在 public void renderFrame(State state, float[] projectionMatrix) 方法里進行

2.3.1 旋轉

rotateM(float[] m, int mOffset, float a, float x, float y, float z)
m為模型, moffse 為偏移量 , a 為旋轉角度 , x 軸, y 軸,z 軸

旋轉 X軸如下
Matrix.rotateM(modelViewMatrix,0,mAngleY, 1, 0, 0);//旋轉

旋轉 Y軸如下
Matrix.rotateM(modelViewMatrix,0,mAngleX, 0, 1, 0);//旋轉

2.3.2 縮放

scaleM(float[] m, int mOffset,float x, float y, float z)
m為模型, moffse 為偏移量 , x 軸, y 軸,z 軸

按照手指距離縮放如下
Matrix.scaleM(modelViewMatrix, 0, kBuildingScale, kBuildingScale, kBuildingScale);

下面貼出詳細代碼,供大家參考

ImageTargetRenderer.java

    // The render function called from SampleAppRendering by using RenderingPrimitives views.
    // The state is owned by SampleAppRenderer which is controlling it's lifecycle.
    // State should not be cached outside this method.
    public void renderFrame(State state, float[] projectionMatrix)
    {
        // Renders video background replacing Renderer.DrawVideoBackground()
        mSampleAppRenderer.renderVideoBackground();

        GLES20.glEnable(GLES20.GL_DEPTH_TEST);

        // handle face culling, we need to detect if we are using reflection
        // to determine the direction of the culling
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        GLES20.glCullFace(GLES20.GL_BACK);

        // Did we find any trackables this frame?
        for (int tIdx = 0; tIdx < state.getNumTrackableResults(); tIdx++) {
            TrackableResult result = state.getTrackableResult(tIdx);
            Trackable trackable = result.getTrackable();
            printUserData(trackable);
            Matrix44F modelViewMatrix_Vuforia = Tool
                    .convertPose2GLMatrix(result.getPose());
            float[] modelViewMatrix = modelViewMatrix_Vuforia.getData();

            int textureIndex = trackable.getName().equalsIgnoreCase("kfc") ? 0
                    : 1;
            //stones

            textureIndex = trackable.getName().equalsIgnoreCase("tarmac") ? 2
                    : textureIndex;



            // deal with the modelview and projection matrices
            float[] modelViewProjection = new float[16];

            Matrix.rotateM(modelViewMatrix,0,mAngleY, 1, 0, 0);//旋轉
            Matrix.rotateM(modelViewMatrix,0,mAngleX, 0, 1, 0);//旋轉

            //Matrix.rotateM(modelViewMatrix, 0, 90.0f, 1.0f, 0, 0);
            Matrix.scaleM(modelViewMatrix, 0, kBuildingScale,
                    kBuildingScale, kBuildingScale);

            Matrix.multiplyMM(modelViewProjection, 0, projectionMatrix, 0, modelViewMatrix, 0);

            // activate the shader program and bind the vertex/normal/tex coords
            GLES20.glUseProgram(shaderProgramID);

            if (!mActivity.isExtendedTrackingActive()) {
                GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT,
                        false, 0, testModel.getVertices());
                        //false, 0, mTeapot.getVertices());
                GLES20.glVertexAttribPointer(textureCoordHandle, 2,
                        GLES20.GL_FLOAT, false, 0, testModel.getTexCoords());
                        //GLES20.GL_FLOAT, false, 0, mTeapot.getTexCoords());


                GLES20.glEnableVertexAttribArray(vertexHandle);
                GLES20.glEnableVertexAttribArray(textureCoordHandle);

                // activate texture 0, bind it, and pass to shader
                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,
                        mTextures.get(textureIndex).mTextureID[0]);
                GLES20.glUniform1i(texSampler2DHandle, 0);

                // pass the model view matrix to the shader
                GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false,
                        modelViewProjection, 0);

                // finally draw the teapot
                GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,
                        testModel.getNumObjectVertex());
                //GLES20.glDrawElements(GLES20.GL_TRIANGLES,
                        //mTeapot.getNumObjectIndex(), GLES20.GL_UNSIGNED_SHORT,
                        //mTeapot.getIndices());

                // disable the enabled arrays
                GLES20.glDisableVertexAttribArray(vertexHandle);
                GLES20.glDisableVertexAttribArray(textureCoordHandle);
            } else {
                GLES20.glDisable(GLES20.GL_CULL_FACE);
                GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT,
                        false, 0, testModel.getVertices());
                        //false, 0, mBuildingsModel.getVertices());
                GLES20.glVertexAttribPointer(textureCoordHandle, 2,
                        GLES20.GL_FLOAT, false, 0, testModel.getTexCoords());
                        //GLES20.GL_FLOAT, false, 0, mBuildingsModel.getTexCoords());

                GLES20.glEnableVertexAttribArray(vertexHandle);
                GLES20.glEnableVertexAttribArray(textureCoordHandle);

                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,
                        mTextures.get(3).mTextureID[0]);
                GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false,
                        modelViewProjection, 0);
                GLES20.glUniform1i(texSampler2DHandle, 0);
                GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,
                        testModel.getNumObjectVertex());
                        //mBuildingsModel.getNumObjectVertex());

                SampleUtils.checkGLError("Renderer DrawBuildings");
            }

            SampleUtils.checkGLError("Render Frame");

        }

        GLES20.glDisable(GLES20.GL_DEPTH_TEST);

    }

ImageTargets.java

    public float  oldDist = 90f;
    private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
    private float mPreviousX;
    private float mPreviousY;
    private float x, y;

    public boolean onTouchEvent(MotionEvent event)

    {

        int pointerCount = event.getPointerCount();

        int action = event.getAction();

        y = event.getY();
        x = event.getX();

        // 單點觸控的情況主要控制模型的旋轉

        if (pointerCount == 1) {

            switch (action) {

                case MotionEvent.ACTION_DOWN:

                    System.out.println("ACTION_DOWN pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_UP:

                    System.out.println("ACTION_UP pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_MOVE:

                    System.out.println("ACTION_MOVE pointerCount=" + pointerCount);

                    float dy = y - mPreviousY;//計算觸控筆Y位移
                    float dx = x - mPreviousX;//計算觸控筆X位移
                    mRenderer.setmAngleX(mRenderer.getmAngleX() + dy * TOUCH_SCALE_FACTOR);//設置沿x軸旋轉角度
                    mRenderer.setmAngleY(mRenderer.getmAngleY() + dx * TOUCH_SCALE_FACTOR);//設置沿y軸旋轉角度
                    break;

            }

        }

        // 兩點觸控的情況主要控制模型的縮放

        if (pointerCount == 2) {

            switch (action & MotionEvent.ACTION_MASK) {

                case MotionEvent.ACTION_DOWN:

                    System.out.println("ACTION_DOWN pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_POINTER_DOWN:

                    oldDist = (float) Math.sqrt((event.getX(0) - event.getX(1)) * (event.getX(0) - event.getX(1)) + (event.getY(0) - event.getY(1)) * (event.getY(0) - event.getY(1)));

                    System.out.println("ACTION_UP pointerCount=" + pointerCount);

                    break;

                case MotionEvent.ACTION_MOVE:

                    System.out.println("ACTION_MOVE pointerCount=" + pointerCount);

                    float newDist = (float) Math.sqrt((event.getX(0) - event.getX(1)) * (event.getX(0) - event.getX(1)) + (event.getY(0) - event.getY(1)) * (event.getY(0) - event.getY(1)));

                    float scale = newDist / oldDist;

                    if (scale >= 1.5f) {

                        scale = 1.5f;

                    } else if (scale <= 0.2f) {

                        scale = 0.2f;

                    }

                    mRenderer.setScale(scale);//調用本地方法傳值

                    break;

            }

        }
        mPreviousY = y;//記錄觸控筆位置
        mPreviousX = x;//記錄觸控筆位置
        return true;

    }

參考:

https://blog.jayway.com/2009/12/03/opengl-es-tutorial-for-android-part-i/

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容