Android OpenGL触摸反馈

xiaoxiao2021-02-28  7

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 Table 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; } } 显示如下
转载请注明原文地址: https://www.6miu.com/read-1250333.html

最新回复(0)