Переведите касание android-opengl в луч / вектор и проверьте, попадает ли он в самолет

Я новичок в разработке приложений для Android / с использованием OpenGL ES. Моя основная цель - создать 4 простых квадрата в моем обзоре поверхности, когда пользователь нажимает на экран, я хочу проверить, какой из квадратов он щелкнул (если есть). Затем этот квадрат должен быть отмечен и изменить свой цвет, когда пользователь щелкает второй (другой) квадрат, я хочу нарисовать стрелку от square1 к square2. Я использовал руководство по Android для opengl es в качестве отправной точки и попытался адаптировать его для моя цель.


У меня проблемы с проверкой, щелкнул ли пользователь прямоугольник. Я проработал довольно много вопросов о stackoverflow и других руководств, касающихся opengl в Android и линейной алгебры в целом. Я считаю, что это самые полезные:
Учебное пособие по Opengl
Выбор мыши с использованием преобразования лучей
Реализовать выбор луча

Вот что я получил это пока:
Мои визуализированные квадраты определены в матрице модель-вид-проекция, чтобы проверить, щелкнул ли пользователь по этому квадрату, мне нужно преобразовать щелчок в луч в координатах мирового пространства. После этого мне нужно будет проверить, не сталкивается ли этот луч с одним из моих квадратов, которые лежат в одной плоскости.

Вот где я редактировал больше всего: в SurfaceCreated я добавляю четыре квадрата и перемещаю их на свои места. Когда пользователь нажимает на экран, вызывается checkCollision-метод с абсолютными координатами экрана. Тогда я попытался перевести инструкции из этих сообщений:
Реализовать выбор лучей
Пересечение линии и плоскости

public class MyGLRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "MyGLRenderer";
    private HashMap<String, Square> mySquares = new HashMap<>();


    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVPMatrix = 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 int screenWidth = 0;
    private int screenHeight = 0;

    private float mAngle;
    private int square_number = 65;
    private final float[][] colors = {
            {0.29f, 0.57f, 1.0f, 1.0f},
            {0.8f, 0.0f, 0.0f, 1.0f},
            {0.13f, 0.8f, 0.0f, 1.0f},
            {1.0f, 0.84f, 0.0f, 1.0f}};


    public void onSurfaceCreated(GL10 unused, EGLConfig config) {

        // Set the background frame color
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);


        //Adding the 4 squares to the grid and move them to their positions
        String square_key = "";
        square_key = addSquare();
        this.mySquares.get(square_key).moveSquare(0.5f, 0.5f);
        square_key = addSquare();
        this.mySquares.get(square_key).moveSquare(0.5f, -0.5f);
        square_key = addSquare();
        this.mySquares.get(square_key).moveSquare(-0.5f, 0.5f);
        square_key = addSquare();
        this.mySquares.get(square_key).moveSquare(-0.5f, -0.5f);

    }


    public void checkCollision(float touchX, float touchY) {
        //Step 1: normalize coordinates
        float[] touchClipMatrix = new float[]{
                2.0f * touchX / this.screenWidth - 1.0f,
                1.0f - touchY * 2 / this.screenHeight,
                0,
                1.0f
        };


        //inverted matrices
        float[] invertedProjectionMatrix = new float[16];
        float[] invertedMViewMatrix = new float[16];
        Matrix.invertM(invertedProjectionMatrix,0, mProjectionMatrix, 0);
        Matrix.invertM(invertedMViewMatrix,0, mViewMatrix, 0);

        //Calculation Matrices
        float[] unviewMatrix = new float[16];
        float[] mouse_worldspace = new float[4];

        //Getting mouse position in world space
        Matrix.multiplyMM(unviewMatrix, 0, invertedMViewMatrix, 0, invertedProjectionMatrix,0);
        Matrix.multiplyMV(mouse_worldspace, 0 , unviewMatrix, 0 , touchClipMatrix, 0);


        Log.i(TAG, "checkCollision-touchClipMatrix: "+ Arrays.toString(touchClipMatrix));
        Log.i(TAG, "checkCollision-invertedProjectionMatrix: "+ Arrays.toString(invertedProjectionMatrix));
        Log.i(TAG, "checkCollision-invertedMViewMatrix: "+ Arrays.toString(invertedMViewMatrix));
        Log.i(TAG, "checkCollision-mouse_worldspace: "+ Arrays.toString(mouse_worldspace));


        //Getting the camera position
        float [] cameraPosition = {0, 0, -3};

        //subtract camera position from the mouse_worldspace
        float [] ray_unnormalized = new float[4];
        for(int i = 0; i < 3; i++){
            ray_unnormalized[i] = mouse_worldspace[i] / mouse_worldspace[3] - cameraPosition[i];
        }

        //normalize ray_vector
        float ray_length = Matrix.length(ray_unnormalized[0], ray_unnormalized[1], ray_unnormalized[2]);
        float [] ray_vector = new float[4];
        for(int i=0; i<3; i++){
            ray_vector[i] = ray_unnormalized[i]/ray_length;
        }
        Log.i(TAG, "checkCollision - ray_vector: "+ Arrays.toString(ray_vector));

        LinePlaneIntersection linePlaneIntersection = new LinePlaneIntersection();
        LinePlaneIntersection.Vector3D rv = new LinePlaneIntersection.Vector3D(ray_vector[0], ray_vector[1], ray_vector[2]);
        LinePlaneIntersection.Vector3D rp = new LinePlaneIntersection.Vector3D(mouse_worldspace[0], mouse_worldspace[1], mouse_worldspace[2]);
        LinePlaneIntersection.Vector3D pn = new LinePlaneIntersection.Vector3D(0.0, 0.0, 0.0);
        LinePlaneIntersection.Vector3D pp = new LinePlaneIntersection.Vector3D(0.0, 0.0, 1.0);
        LinePlaneIntersection.Vector3D ip = linePlaneIntersection.intersectPoint(rv, rp, pn, pp);
        Log.i(TAG, "checkCollision-intersection point: "+ip);
    }

    public String addSquare() {
        String keyName = String.valueOf((char) this.square_number);
        this.mySquares.put(keyName, new Square(keyName, colors[this.square_number-65]));
        this.square_number += 1;
        return keyName;
    }


    public void logMatrices() {
        Log.i(TAG, "MVPMatrice: " + Arrays.toString(this.mMVPMatrix));
        Log.i(TAG, "mProjectionMarice: " + Arrays.toString(this.mProjectionMatrix));
        Log.i(TAG, "mViewMatrice: " + Arrays.toString(this.mViewMatrix));
    }

@Override
    public void onDrawFrame(GL10 unused) {
        float[] scratch = new float[16];
        // Draw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        // Set the camera position (View matrix)
        //mySquare.moveSquare(0.25f, 0.25f);
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0.0f, 0f, 1.0f, 0.0f);
//        Matrix.scaleM(mViewMatrix, 0, 0.5f,0.5f,0);
//        Matrix.translateM(mViewMatrix, 0, 2f, 1f, 0);
        // Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);



        // Create a rotation for the square
        Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0.0f, 1.0f);
        // Combine the rotation matrix with the projection and camera view
        // Note that the mMVPMatrix factor *must be first* in order
        // for the matrix multiplication product to be correct.
        Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);
        // Draw squares
        for (Map.Entry<String, Square> s : this.mySquares.entrySet()) {
            s.getValue().draw(scratch);
        }
    }

@Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {

        this.screenWidth = width;
        this.screenHeight = height;
        // Adjust the viewport based on geometry changes,
        // such as screen rotation
        GLES20.glViewport(0, 0, width, height);
        float ratio = (float) width / height;
        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

  
    public static int loadShader(int type, String shaderCode) {
        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);
        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

    public static void checkGlError(String glOperation) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e(TAG, glOperation + ": glError " + error);
            throw new RuntimeException(glOperation + ": glError " + error);
        }
    }
}

Я добавил метод moveSquare, потому что все квадраты имеют одинаковые координаты при инициализации. Я не уверен, что это правильный способ сделать это, пожалуйста, скажите мне, если это неправильно / испортил другие вычисления.

public class Square {

    private String squareID;
    private final String vertexShaderCode =
            // This matrix member variable provides a hook to manipulate
            // the coordinates of the objects that use this vertex shader
            "uniform mat4 uMVPMatrix;" +
                    "attribute vec4 squarePosition;" +
                    "void main() {" +
                    // The matrix must be included as a modifier of gl_Position.
                    // Note that the uMVPMatrix factor *must be first* in order
                    // for the matrix multiplication product to be correct.
                    "  gl_Position = uMVPMatrix * squarePosition;" +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 squareColor;" +
                    "void main() {" +
                    "  gl_FragColor = squareColor;" +
                    "}";

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;
    private int mProgram;
    private int mPositionHandle;
    private int mColorHandle;
    private int mMVPMatrixHandle;

    private static final String TAG = "Square";

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;

    private float squareCoords[] = {
            -0.1f, 0.1f, 0.0f,   // top left
            -0.1f, -0.1f, 0.0f,   // bottom left
            0.1f, -0.1f, 0.0f,   // bottom right
            0.1f, 0.1f, 0.0f}; // top right

    private final short drawOrder[] = {0, 1, 2, 0, 2, 3}; // order to draw vertices

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    //Fallback color
    private float color[] = {0.2f, 0.709803922f, 0.898039216f, 1.0f};

    /**
     * Sets up the drawing object data for use in an OpenGL ES context.
     */
    public Square(String id, float [] color) {
        this.squareID = id;
        if(color.length == 4) {
            this.color = color;
        }

        //Buffers need to updated with the new square coordinates
        updateBuffers();

        //Shaders (should) only be prepared once when initializing a square
        prepareShadersAndOpenGL();
    }


    private void prepareShadersAndOpenGL() {
        // prepare shaders and OpenGL program
        int vertexShader = MyGLRenderer.loadShader(
                GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(
                GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        mProgram = GLES20.glCreateProgram();             // create empty OpenGL Program
        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
        GLES20.glLinkProgram(mProgram);                    // create OpenGL program executables
    }

    public void updateBuffers() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);

        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }


    //Updating the square coordinates and updating to buffers
    public void moveSquare(float deltaX, float deltaY) {
        this.squareCoords[0] += deltaX;
        this.squareCoords[3] += deltaX;
        this.squareCoords[6] += deltaX;
        this.squareCoords[9] += deltaX;
        this.squareCoords[1] += deltaY;
        this.squareCoords[4] += deltaY;
        this.squareCoords[7] += deltaY;
        this.squareCoords[10] += deltaY;

        updateBuffers();
    }
    

    /**
     * Encapsulates the OpenGL ES instructions for drawing this shape.
     *
     * @param mvpMatrix - The Model View Project matrix in which to draw
     *                  this shape.
     */
    public void draw(float[] mvpMatrix) {
        // Add program to OpenGL environment
//        Log.i(TAG, "Square ("+squareID+") mProgram: "+mProgram);
        GLES20.glUseProgram(mProgram);



        // get handle to vertex shader's vPosition member
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "squarePosition");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(
                mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "squareColor");

        // Set color for drawing the triangle
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // get handle to shape's transformation matrix
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
//        MyGLRenderer.checkGlError("glGetUniformLocation");

        // Apply the projection and view transformation
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
//        MyGLRenderer.checkGlError("glUniformMatrix4fv");

        // Draw the square
        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, drawOrder.length,
                GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}
public class MyGLSurfaceView extends GLSurfaceView {

    private final MyGLRenderer mRenderer;
    private static final String TAG = "MyGLSurfaceView";
    private final float TOUCH_SCALE_FACTOR = 180.0f / 320;

    public MyGLSurfaceView(Context context) {
        super(context);
        // Create an OpenGL ES 2.0 context.
        setEGLContextClientVersion(2);
        // Set the Renderer for drawing on the GLSurfaceView
        mRenderer = new MyGLRenderer();
        setRenderer(mRenderer);
        // Render the view only when there is a change in the drawing data
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }


    @Override
    public boolean onTouchEvent(MotionEvent e) {
        // MotionEvent reports input details from the touch screen
        // and other input controls. In this case, you are only
        // interested in events where the touch position changed.
        float x = e.getX();
        float y = e.getY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mRenderer.logMatrices();
                mRenderer.checkCollision(x, y);
                //  mRenderer.setAngle(mRenderer.getAngle()+45f);
                requestRender();
        }
        return true;
    }
}

Я знаю, что это довольно много для чтения, поэтому я постараюсь выразить свои основные вопросы / проблемы:

  1. Верна ли моя идея в целом или я использую неправильные преобразования / шаги?
  2. Представляет ли squareCoord-Array в классе Square мою модель-матрицу и в какой момент они преобразуются в мировые координаты?
  3. Почему матрица, которую я передаю методу рисования в квадратном классе, называется mMVPMatrix, для меня это означает, что эта матрица содержит все три матрицы (модель, представление, проекция). Но в тот момент, когда я называю метод рисования, я только что умножил проекцию на матрицу вида, так откуда же взяться части модели? Я что-то здесь упускаю или путаю термины для матриц?
  4. Я все еще пытаюсь понять, что делает / описывает Матрица Проекции. Я понял, что он в основном определяет область, которая будет отображаться, все, что не в этой области, не будет отображаться на экране. Всегда ли эта область относительно положения камеры (обзора)?

Надеюсь, я правильно объяснил свои проблемы, может быть, есть даже более простое решение моей проблемы в целом. Заранее спасибо всем, кто прочитал до сих пор. Я надеюсь, что кто-нибудь сможет мне с этим помочь

PS: Это мой первый вопрос по Stackoverflow, и мое написание может быть не идеальным, извините за это. Если вам не хватает информации, чтобы понять проблему / ответить на один из моих вопросов, просто задайте, я постараюсь добавить их как можно быстрее.


Вот некоторая отладочная информация:

  • Распознанное касание на позиции x = 940.94604 | у = 407,9297
  • MVPMatrix: [-4,4, 0,0, 0,0, 0,0, 0,0, 3,0, 0,0, 0,0, 0,0, 0,0, 2,5, 1,0, 0,0, 0,0, -3,0, 3,0]
  • mProjectionMarix: [4,4, 0,0, 0,0, 0,0, 0,0, 3,0, 0,0, 0,0, 0,0, 0,0, -2,5, -1,0, 0,0, 0,0, -10,5, 0,0]
  • mViewMatrix: [-1,0, 0,0, -0,0, 0,0, 0,0, 1,0, -0,0, 0,0, 0,0, -0,0, -1,0, 0,0, 0,0, 0,0, -3,0, 1,0]
  • checkCollision-touchClipMatrix: [0.7424927, 0.48493725, -3.0, 1.0]
  • checkCollision-IntedProjectionMatrix: [0.22727272, -0.0, -0.0, -0.0, -0.0, 0.3333333, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0952381, -0.0, -0.0, -1.0, 0.23809522]
  • checkCollision-инвертированныйMViewMatrix: [-1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, -0.0, -3.0, 1.0]
  • checkCollision-unview-Matrix [-0.22727272, 0,0, 0,0, 0,0, 0,0, 0,3333333, 0,0, 0,0, 0,0, 0,0, 0,2857143, -0,0952381, 0,0, -0,0, 0,28571433, 0,23809522]
  • checkCollision-mouse_worldspace: [-0.16874833, 0.16164574, -0.5714286, 0.52380955]
  • checkCollision-ray_unnormalized: [-0.3221559, 0.3085964, 1.9090909, 0.0]
  • checkCollision-ray_length: 1.9605213
  • checkCollision - ray_vector: [-0.16432154, 0.15740527, 0.9737669, 0.0]
  • checkCollision-точка пересечения: (NaN, NaN, NaN)

person yBoBy    schedule 27.08.2020    source источник


Ответы (1)


Вычисление ray_unnormalized кажется неправильным. Вы не можете вычесть однородные координаты, как вы это делаете. Преобразуйте mouse_worldspace в декартову координату. Декартовы координаты - это частные x, y, компонент z и компонент w (см. Разделение перспективы).
Направление луча - это вектор от декартовой позиции камеры к декартовой позиции мыши:

//Getting the camera position
float [] cameraPosition = {0, 0, -6};

//subtract camera position from the mouse_worldspace
float [] ray_unnormalized = new float[4];
for(int i = 0; i < 3; i++){
    ray_unnormalized[i] = mouse_worldspace[i] / mouse_worldspace[3] - cameraPosition[i];
}
person Rabbid76    schedule 27.08.2020
comment
Спасибо за ваш быстрый ответ, я выполнил ваше предложение и обновил свой вопрос, но получаемые мной результаты по-прежнему неверны / непригодны для использования. Я также добавил некоторую отладочную информацию. - person yBoBy; 28.08.2020