您的位置:首页 > 移动开发 > Android开发

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; }

}
显示如下

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: