系列教程:
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軸的正方向,則稱這個坐標系為右手直角坐標系。
1.2 Translate 平移變換
方法 public abstract void glTranslatef (float x, float y, float z)
用于坐標平移變換。
1.3 Rotate 旋轉
方法 public abstract void glRotatef(float angle, float x, float y, float z)
用來實現選擇坐標變換,單位為角度。(x,y,z)定義旋轉的參照矢量方向。多次旋轉的順序非常重要。
1.4 Scale(縮放)
方法 public abstract void glScalef (float x, float y, float z)
用于縮放變換。
下圖為使用 gl.glScalef(2f, 2f, 2f)
變換后的基本,相當于把每個坐標值都乘以2.
1.5 組合
Translate & Rotate (平移和旋轉組合變換)
在對 Mesh(網格,構成三維形體的基本單位)同時進行平移和選擇變換時,坐標變換的順序也直接影響最終的結果。
比如:先平移后旋轉,旋轉的中心為平移后的坐標。
先選擇后平移: 平移在則相對于旋轉后的坐標系:
一個基本原則是,坐標變換都是相對于變換的 Mesh 本身的坐標系而進行的。
Translate & Scale(平移和縮放組合變換)
同樣當需要平移和縮放時,變換的順序也會影響最終結果。
比如先平移后縮放:
gl.glTranslatef(2, 0, 0);
gl.glScalef(0.5f, 0.5f, 0.5f);
如果調換一下順序:
gl.glScalef(0.5f, 0.5f, 0.5f);
gl.glTranslatef(2, 0, 0);
結果就有所不同:
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/