1

我一直在尝试让我的游戏能够处理 OpenGL 中的用户交互。我有一个圆圈,我希望能够检测到用户的按压。我知道我需要使用Ray Picking,我已经按照这本书OpenGl 2 for Android并按照教程在 3D 空间中创建空气曲棍球游戏并检测用户点击事件。

现在我希望利用我的知识从 2D 游戏开始,但是遵循教程并应用我目前的知识并不能帮助我实现能够检测到用户点击我的对象。

我假设我做错了什么,为 3D 和 2D 做这件事略有不同。

我有一个几何类如下:

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

       public Point translateY(float distance) {
           return new Point(x, y + distance, z);
       }

       public Point translate(Vector vector) {
           return new Point(
               x + vector.x,
               y + vector.y,
               z + vector.z);
       }
   }

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

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

       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 Ray {
       public final Point point;
       public final Vector vector;

       public Ray(Point point, Vector vector) {
           this.point = point;
           this.vector = vector;
       }
   }

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

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

我有一个表面视图 onTouchEvent 覆盖如下:

if(event.getAction() == MotionEvent.ACTION_DOWN){

    if (event != null) {

        final float normalizedX =
                        (event.getX() / (float) getWidth()) * 2 - 1;
        final float normalizedY =
                        -((event.getY() / (float) getHeight()) * 2 - 1);

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            queueEvent(new Runnable() {
                @Override
                    public void run() {
                        mRenderer.handleTouchPress(
                            normalizedX, normalizedY);
                        }
                    });

                }

                return true;
            } else {
                return false;
       }
}

最后我有一个渲染器如下:

public class ImpulseRushRenderer implements Renderer {
    private Geometry.Point circlePosition;
    private boolean circlePressed= false;
    private final Context context;
    public Circle circle;
    private float mMatrix[] = new float[16];
    private float[] mTempMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    private final float[] mRotationMatrix = new float[16];
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjMatrix = new float[16];
    private final float[] mVMatrix = new float[16];
    private final float[] mModelMatrix = new float[16];
    private final float[] tempMatrix = new float[16];
    private final float[] invertedViewProjectionMatrix = new float[16];

    public ImpulseRushRenderer(Context context) {
        this.context = context;
    }

    LayoutInflater mInflater;

    @Override
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
        glClearColor(0.1725490196078431f, 0.2431372549019608f, 0.3137254901960784f, 1.0f);

        circle= new Circle();
        mInflater = (LayoutInflater)    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {
        glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 glUnused) {

        glClear(GL_COLOR_BUFFER_BIT);

        Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);

        mTempMatrix = mModelMatrix.clone();
        Matrix.multiplyMM(mModelMatrix, 0, mTempMatrix, 0, mRotationMatrix, 0);

        mTempMatrix = mMVPMatrix.clone();
        Matrix.multiplyMM(mMVPMatrix, 0, mTempMatrix, 0, mModelMatrix, 0);
        Matrix.orthoM(mMatrix, 0, -1, 1, -1, 1, -1, 1);

        Matrix.orthoM(mMatrix, 0, -1, 1, -1, 1, -1, 1);

        Matrix.setIdentityM(mModelMatrix, 0);

        circle.draw(mModelMatrix);

        circlePosition= new Geometry.Point(0f, 100 / 2f, 0.4f);
    }

    private 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];

        multiplyMV(
            nearPointWorld, 0, invertedViewProjectionMatrix, 0, nearPointNdc, 0);
        multiplyMV(
            farPointWorld, 0, invertedViewProjectionMatrix, 0, farPointNdc, 0);

        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 Ray(nearPointRay,
            Geometry.vectorBetween(nearPointRay, farPointRay));
    }

    public static int loadShader(int type, String shaderCode) {

        int shader = GLES20.glCreateShader(type);

        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    public void handleTouchPress(float normalizedX, float normalizedY) {
        Geometry.Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);

        Geometry.Sphere circleBoundingSphere = new Geometry.Sphere(new Geometry.Point(
            circlePosition.x,
            circlePosition.y,
            circlePosition.z),
            100 / 2f);

        circlePressed= Geometry.intersects(circleBoundingSphere , ray);

        if (circlePressed) {
            Log.i("circlePressed", "circle was pressed");
        }
    }


    private void divideByW(float[] vector) {
        vector[0] /= vector[3];
        vector[1] /= vector[3];
        vector[2] /= vector[3];
    }

}

据我目前的知识和那本书中的教程,我链接这应该是正确的。

4

1 回答 1

2

在 "private Ray convertNormalized2DPointToRay" 中,您可以尝试将 1 放入 nearPointWorld[3] 和 farPointWorld[3](1 到 W),否则乘法运算符将无法正常工作。

编辑

方法一:距离函数

假设你已经有了触摸位置,物体在同一空间的中心和它的半径。

int touchX, touchY;
int centerObjX, centerObjY;
int rayon;

int distX = centerObjX - touchX; // the sens don't matter
int distY = centerObjY - touchY; 
if (distX*distX + distY*distY<rayon*rayon) // squared distance
{
    // touch in the circle
}

注意:您可以通过模型视图乘以中心来获得相机移动的中心。这种方法很好,因为它是数学的并且不需要使用 GPU。您可以在这里拥有一些 3D 距离功能以及如何使用

方法 2:颜色选择(算法)

  1. 将背景清除为黑色 (rgb=(0,0,0))
  2. 将颜色对象设置为 rgb=(1,0,0) (现在是对象的颜色 ID)
  3. 使用 glReadPixels() 选择颜色(例如)
  4. 然后比较检索到的颜色值以了解它是否是您的对象。

当场景中有很多对象或对象很复杂时,此方法非常有用。

您的方法也很棒,但结果只是您单击的位置(或方向相机->您单击的位置)。然后,它更适合 3D 使用。

于 2016-05-17T10:06:41.070 回答