vuforia android 教程(4) 添加手势识别 手势操
系列教程:
vuforia android 教程(1)
https://www.jianshu.com/p/37b158175b04
vuforia android 教程(2)
https://www.jianshu.com/p/2d70974857b8
vuforia android 教程(3)
https://www.jianshu.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/