【问题标题】:Ray Picking for user Touching object in Open Gl 2D AndroidOpen GL 2D Android中用户触摸对象的光线拾取
【发布时间】:2016-05-16 23:59:54
【问题描述】:

我一直在尝试让我的游戏能够处理 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];
    }

}

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

【问题讨论】:

    标签: java android-studio matrix opengl-es ray-picking


    【解决方案1】:

    在“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距离函数以及如何使用here

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

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

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

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

    【讨论】:

    • 你的意思是这样? multiplyMV(1, 0, reverseViewProjectionMatrix, 0, nearPointNdc, 0);
    • 还有为什么它可以用于 2D。我今晚下班回家后会尽快尝试。
    • 哦,我没看到它是 2D 的,抱歉。我曾经为 3D 精度制作 unproject 方法。您可以选择另一种方法来获取对象,例如颜色选择(使用唯一颜色为对象着色以获取“对象 ID”)或简单的鼠标计算(对于圆,中心和鼠标之间的距离
    • 这适用于安卓没有鼠标。你能提供一个代码示例吗?我看到的每个地方都说要使用光线拾取,我永远无法在 2D 对象上点击在线找到任何东西。谢谢
    • 自 2D 以来,颜色选择绝对是一个更好的主意。谢谢。如果您还可以提供代码示例如何进行颜色选择或 url 参考 id 非常感谢。再次感谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多