Android OpenGL触摸反馈
2017-08-07 09:19
369 查看
Android OpenGL触摸反馈
首先申明下,本文为笔者学习《OpenGL ES应用开发实践指南》的笔记,并加入笔者自己的理解和归纳总结。1、添加触摸支持
通过调用setOnTouchListener方法监听视图的触控事件,然后把触控位置转化为[-1, 1]的归一化设备坐标,最后转发给渲染器。
private OpenGLTouchShaderRender mTouchShaderRender; mSurfaceView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event != null) { final float normalizedX = (event.getX() / (float) v.getWidth()) * 2 - 1; final float normalizedY = -((event.getY() / (float) v.getHeight()) * 2 - 1); if (event.getAction() == MotionEvent.ACTION_DOWN) { mSurfaceView.queueEvent(new Runnable() { @Override public void run() { if (mTouchShaderRender != null) mTouchShaderRender.handleTouchPress(normalizedX, normalizedY); } }); } else if (event.getAction() == MotionEvent.ACTION_MOVE) { mSurfaceView.queueEvent(new Runnable() { @Override public void run() { if (mTouchShaderRender != null) mTouchShaderRender.handleTouchDrag(normalizedX, normalizedY); } }); } return true; } return false; } });
2、二维点扩展成三维直线
当我们把一个三维场景投递到二维屏幕的时候,我们使用透视投影和透视除法把顶点坐标变换为归一化设备坐标。
现在我们有被触摸点的归一化设备坐标,为了把为触摸点转换为一个三维射线,我们需要取消透视投影和透视除法。
private final float[] invertedViewProjectMatrix = new float[16]; Matrix.invertM(invertedViewProjectMatrix, 0, viewProjectMatrix, 0);为了把被触摸点映射到一条射线,我们在归一化设备坐标里设置了两个点:其中一个点是z值为-1,而另一个点是z值为1的点。
private Geometry.Ray convertNormalized2DPointToRay(float normalizedX, float normalizedY) { final float[] nearPointNdc = {normalizedX, normalizedY, -1, 1}; final float[] farPointNdc = {normalizedX, normalizedY, 1, 1}; final float[] nearPointWorld = new float[4]; final float[] farPointWorld = new float[4]; Matrix.multiplyMV(nearPointWorld, 0, invertedViewProjectMatrix, 0, nearPointNdc, 0); Matrix.multiplyMV(farPointWorld, 0, invertedViewProjectMatrix, 0, farPointNdc, 0); // 把x, y, z除以这些反转的w,这样就撤销了透视除法的影响 divideByW(nearPointWorld); divideByW(farPointWorld); Geometry.Point nearPointRay = new Geometry.Point(nearPointWorld[0], nearPointWorld[1], nearPointWorld[2]); Geometry.Point farPointRay = new Geometry.Point(farPointWorld[0], farPointWorld[1], farPointWorld[2]); // 返回两点之间的射线 return new Geometry.Ray(nearPointRay, Geometry.vectorBetween(nearPointRay, farPointRay)); } private void divideByW(float[] vector) { vector[0] /= vector[3]; vector[1] /= vector[3]; vector[2] /= vector[3]; }在Geometry中定义射线类Ray,矢量类Vector
public static class Ray { public final Point point; public final Vector vector; public Ray(Point point, Vector vector) { this.point = point; this.vector = vector; } } public static class Vector { public final float x, y, z; public Vector(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } } public static Vector vectorBetween(Point from, Point to) { return new Vector(to.x - from.x, to.y - from.y, to.z - from.z); }
3、相交测试
我们假定木锥是一个球体,这样相交测试就会相当容易。
在onSurfaceCreated中定义蓝色木锥初始位置
blueMalletPoint = new Geometry.Point(0, -0.4f, mMallet.height / 2);定义一个球体,判断是否与射线相交
Geometry.Sphere malletBoundingSphere = new Geometry.Sphere(blueMalletPoint, mMallet.height/2); malletPressed = Geometry.intersects(malletBoundingSphere, ray);主要方法是计算圆心到射线之间的距离,和圆的半径比较。而这个距离是三角形的面积*2/射线长度。里面的数学原理笔者也不是很明白,只能照搬过来。
public static boolean intersects(Sphere sphere, Ray ray) { return distanceBetween(sphere.center, ray) < sphere.radius; } private static float distanceBetween(Point point, Ray ray) { Vector p1ToPoint = vectorBetween(ray.point, point); Vector p2ToPoint = vectorBetween(ray.point.translate(ray.vector), point); float areaOfTriangleTimesTwo = p1ToPoint.crossProduct(p2ToPoint).length(); float lengthOfBase = ray.vector.length(); float distanceFromPointToRay = areaOfTriangleTimesTwo / lengthOfBase; return distanceFromPointToRay; }Vector类
public static class Vector { public float length() { return (float)Math.sqrt( x * x + y * y + z * z); } public Vector crossProduct(Vector other) { return new Vector( (y * other.z) - (z * other.y), (z * other.x) - (x * other.z), (x * other.y) - (y * other.x)); } }
4、移动物体
首先需要手指按住木锥,然后把拖动的点转换成射线,找到这条射线和桌子的相交点,最后把木锥移动到那个点。
public void handleTouchDrag(float normalizedX, float normalizedY) { if (malletPressed) { Geometry.Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY); // 平面位于(0, 0, 0),有一个法向向量(0, 0, 1) Geometry.Plane plane = new Geometry.Plane(new Geometry.Point(0, 0, 0), new Geometry.Vector(0, 0, 1)); Geometry.Point touchedPoint = Geometry.intersectionPoint(ray, plane); blueMalletPoint = new Geometry.Point(touchedPoint.x, touchedPoint.y, mMallet.height/2f); } }Plane类,定义了一个平面,它包含一个法向向量和平面上一个点
public static class Plane { public final Point point; public final Vector normal; public Plane(Point point, Vector normal) { this.point = point; this.normal = normal; } }intersectionPoint计算相交点,里面的数学原理笔者也不是很明白,只能照搬过来。
public static Point intersectionPoint(Ray ray, Plane plane) { Vector rayToPlaneVector = vectorBetween(ray.point, plane.point); float scaleFactor = rayToPlaneVector.dotProduct(plane.normal) / ray.vector.dotProduct(plane.normal); Point intersectionPoint = ray.point.translate(ray.vector.scale(scaleFactor)); return intersectionPoint; }Vector类
public static class Vector { public float dotProduct(Vector other) { return x * other.x + y * other.y + z * other.z; } public Vector scale(float f) { return new Vector(x * f, y * f, z * f); } }
5、OpenGLTouchShaderRender类
class OpenGLTouchShaderRender implements GLSurfaceView.Renderer {
private boolean malletPressed = false;
private Geometry.Point blueMalletPoint;
private final float[] invertedViewProjectionMatrix = new float[16];
private final float[] projectionMatrix = new float[16];
private final float[] modelMatrix = new float[16];
private final float[] viewMatrix = new float[16];
private final float[] viewProjectionMatrix = new float[16];
private final float[] modelViewProjectionMatrix = new float[16];
private TextureProgram mTextureProgram;
private GeometryColorProgram mColorProgram;
private T
4000
able mTable;
private GeometryMallet mMallet;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mTable = new Table();
// 创建木锥
ResourceDataBuilder.ResourceData data = ResourceDataBuilder.createMallet(
new Geometry.Cylinder(new Geometry.Point(0f, 0f, 0f), 0.08f, 0.15f), 32);
mMallet = new GeometryMallet(data, 0.08f, 0.15f);
blueMalletPoint = new Geometry.Point(0, -0.4f, mMallet.height / 2);
mTextureProgram = new TextureProgram(OpenGLTouchShaderActivity.this,
R.drawable.air_hockey_surface);
mColorProgram = new GeometryColorProgram(OpenGLTouchShaderActivity.this);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
// 创建透视投影
Matrix.perspectiveM(projectionMatrix, 0, 45, (float)width / (float)height, 1, 10);
Matrix.setLookAtM(viewMatrix, 0, 0f, -2.4f, 1.4f, 0f, 0f, 0f, 0f, 1f, 0f);
Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
Matrix.invertM(invertedViewProjectionMatrix, 0, viewProjectionMatrix, 0);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 绘制桌子
positionTableInScene();
mTextureProgram.setUniform(modelViewProjectionMatrix);
mTable.bindData(mTextureProgram);
mTable.draw();
// 绘制蓝色木锥
positionObjectInScene(blueMalletPoint.x, blueMalletPoint.y, blueMalletPoint.z);
mColorProgram.setUniform(modelViewProjectionMatrix);
mColorProgram.setColor(0f, 0f, 1f);
mMallet.bindData(mColorProgram);
mMallet.draw();
}
private void positionTableInScene() {
Matrix.setIdentityM(modelMatrix, 0);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix,
0, modelMatrix, 0);
}
// 移动木锥
private void positionObjectInScene(float x, float y, float z) {
Matrix.setIdentityM(modelMatrix, 0);
Matrix.translateM(modelMatrix, 0, x, y, z);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix,
0, modelMatrix, 0);
}
public void handleTouchPress(float normalizedX, float normalizedY) {
Geometry.Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);
// 定义一个球体,判断是否与射线相交
Geometry.Sphere malletBoundingSphere = new Geometry.Sphere(blueMalletPoint, mMallet.height/2); malletPressed = Geometry.intersects(malletBoundingSphere, ray);
LogUtil.log("OpenGLTouchShaderRender", "pressed: " + malletPressed);
}
public void handleTouchDrag(float normalizedX, float normalizedY) {
if (malletPressed) {
Geometry.Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);
Geometry.Plane plane = new Geometry.Plane(new Geometry.Point(0, 0, 0),
new Geometry.Vector(0, 0, 1));
Geometry.Point touchedPoint = Geometry.intersectionPoint(ray, plane);
blueMalletPoint = new Geometry.Point(touchedPoint.x, touchedPoint.y,
mMallet.height/2f);
}
}
private Geometry.Ray convertNormalized2DPointToRay(float normalizedX, float normalizedY) {
LogUtil.log("OpenGLTouchShaderRender", "normalizedX: " + normalizedX);
LogUtil.log("OpenGLTouchShaderRender", "normalizedY: " + normalizedY);
final float[] nearPointNdc = {normalizedX, normalizedY, -1, 1};
final float[] farPointNdc = {normalizedX, normalizedY, 1, 1};
final float[] nearPointWorld = new float[4];
final float[] farPointWorld = new float[4];
Matrix.multiplyMV(nearPointWorld, 0, invertedViewProjectionMatrix, 0, nearPointNdc, 0);
Matrix.multiplyMV(farPointWorld, 0, invertedViewProjectionMatrix, 0, farPointNdc, 0);
// 把x, y, z除以这些反转的w,这样就撤销了透视除法的影响
divideByW(nearPointWorld);
divideByW(farPointWorld);
Geometry.Point nearPointRay =
new Geometry.Point(nearPointWorld[0], nearPointWorld[1], nearPointWorld[2]);
Geometry.Point farPointRay =
new Geometry.Point(farPointWorld[0], farPointWorld[1], farPointWorld[2]);
// 返回两点之间的射线
return new Geometry.Ray(nearPointRay,
Geometry.vectorBetween(nearPointRay, farPointRay));
}
private void divideByW(float[] vector) {
vector[0] /= vector[3];
vector[1] /= vector[3];
vector[2] /= vector[3];
}
}
6、Geometry类
public class Geometry {显示如下
public static class Point {
public final float x, y, z;
public Point(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
// Z轴平移
public Point translateZ(float distance) {
return new Point(x, y, z + distance);
}
// 添加translate方法
public Point translate(Vector vector) {
return new Point(x + vector.x, y + vector.y, z + vector.z);
}
}
public static class Circle {
public final Point center;
public final float radius;
public Circle(Point center, float radius) {
this.center = center;
this.radius = radius;
}
// 缩放半径
public Circle scale(float scale) {
return new Circle(center, radius * scale);
}
}
public static class Cylinder {
public final Point center;
public final float radius;
public final float height;
public Cylinder(Point center, float radius, float height) {
this.center = center;
this.radius = radius;
this.height = height;
}
}
public static class Ray {
public final Point point;
public final Vector vector;
public Ray(Point point, Vector vector) {
this.point = point;
this.vector = vector;
}
}
public static class Vector {
public final float x, y, z;
public Vector(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
public float length() {
return (float)Math.sqrt(
x * x + y * y + z * z);
}
// http://en.wikipedia.org/wiki/Cross_product public Vector crossProduct(Vector other) {
return new Vector(
(y * other.z) - (z * other.y),
(z * other.x) - (x * other.z),
(x * other.y) - (y * other.x));
}
// http://en.wikipedia.org/wiki/Dot_product public float dotProduct(Vector other) {
return x * other.x + y * other.y + z * other.z;
}
public Vector scale(float f) {
return new Vector(x * f, y * f, z * f);
}
}
public static class Sphere {
public final Point center;
public final float radius;
public Sphere(Point center, float radius) {
this.center = center;
this.radius = radius;
}
}
public static class Plane { public final Point point; public final Vector normal; public Plane(Point point, Vector normal) { this.point = point; this.normal = normal; } }
public static Vector vectorBetween(Point from, Point to) {
return new Vector(to.x - from.x, to.y - from.y, to.z - from.z);
}
public static boolean intersects(Sphere sphere, Ray ray) { return distanceBetween(sphere.center, ray) < sphere.radius; } private static float distanceBetween(Point point, Ray ray) { Vector p1ToPoint = vectorBetween(ray.point, point); Vector p2ToPoint = vectorBetween(ray.point.translate(ray.vector), point); float areaOfTriangleTimesTwo = p1ToPoint.crossProduct(p2ToPoint).length(); float lengthOfBase = ray.vector.length(); float distanceFromPointToRay = areaOfTriangleTimesTwo / lengthOfBase; return distanceFromPointToRay; }
public static Point intersectionPoint(Ray ray, Plane plane) { Vector rayToPlaneVector = vectorBetween(ray.point, plane.point); float scaleFactor = rayToPlaneVector.dotProduct(plane.normal) / ray.vector.dotProduct(plane.normal); Point intersectionPoint = ray.point.translate(ray.vector.scale(scaleFactor)); return intersectionPoint; }
}
相关文章推荐
- android Lollipop(5.0)--touch feedback(触摸反馈)
- Android官方开发文档Training系列课程中文版:OpenGL绘图之响应触摸事件
- Android_view的触摸反馈
- 【Android 初级知识】文字颜色 背景 触摸点击 反馈色 之selector的那些事儿
- Android Material Design动画 Touch Feedback | 触摸反馈
- Android——View的触摸传递机制
- OpenGL ES Tutorial for Android – Part V – More on Meshes
- Android应用如何监听自己是否被卸载及卸载反馈功能的实现(转)
- Android的触摸屏和触摸按键的支持
- Android OpenGL教程-第二课【转】
- Android TextureView OpenGL场景
- Android OpenGL教程-第四课【转】
- Android OpenGL教程-第五课【转】
- android游戏开发之我的小小游戏2——连连看游戏2之绘制基本的界面及触摸相关
- android触摸event传递源码分析二
- 老生常谈Android HapticFeedback(震动反馈)
- Android中用OpenGL ES Tracer分析绘制过程
- Android之触摸手势检测GestureDetector使用详解
- [OpenGL]从零开始写一个Android平台下的全景视频播放器——1.4 用OpenGL ES 2.0显示一张图片(下)
- Android opengl 立方体 多纹理