I am trying to implement object picking based on touch coordinates via an intersecting ray test. I am having trouble finding information on converting the touch coordinates to the coordinate system used in the world in order to construct this ray.
My understanding so far is that the matrix that is applied to each vertex in the scene is:
projectionMatrix * viewMatrix * modelMatrix
Here is my process for reversing that process in a an attempt to find the ray's endpoint in the scene as well as my drawing loop in case I'm simply applying the different matrices incorrectly:
public float[] getMouseRayProjection(float touchX, float touchY, float windowWidth, float windowHeight, float[] modelView, float[] projection)
{
float[] rayDirection = new float[4];
float normalizedX = 2 * touchX/windowWidth - 1;
float normalizedY = 1 - 2*touchY/windowHeight;
float[] unviewMatrix = new float[16];
float[] viewMatrix = new float[16];
Matrix.multiplyMM(viewMatrix, 0, projection, 0, modelView, 0);
Matrix.invertM(unviewMatrix, 0, viewMatrix, 0);
float[] nearPoint = multiplyMat4ByVec4(projection, new float[]{normalizedX, normalizedY, 0, 1});
float[] modelviewInverse = new float[16];
Matrix.invertM(modelviewInverse, 0, modelView, 0);
float[] cameraPos = new float[4];
cameraPos[0] = modelviewInverse[12];
cameraPos[1] = modelviewInverse[13];
cameraPos[2] = modelviewInverse[14];
cameraPos[3] = modelviewInverse[15];
rayDirection[0] = nearPoint[0] - cameraPos[0];
rayDirection[1] = nearPoint[1] - cameraPos[1];
rayDirection[2] = nearPoint[2] - cameraPos[2];
rayDirection[3] = nearPoint[3] - cameraPos[3];
return rayDirection;
}
public float[] multiplyMat4ByVec4(float[] matrix4, float[] vector4)
{
float[] returnMatrix = new float[4];
returnMatrix[0] = (matrix4[0] * vector4[0]) + (matrix4[1] * vector4[1]) + (matrix4[2] * vector4[2]) + (matrix4[3] * vector4[3]);
returnMatrix[1] = (matrix4[4] * vector4[0]) + (matrix4[5] * vector4[1]) + (matrix4[6] * vector4[2]) + (matrix4[7] * vector4[3]);
returnMatrix[2] = (matrix4[8] * vector4[0]) + (matrix4[9] * vector4[1]) + (matrix4[10] * vector4[2]) + (matrix4[11] * vector4[3]);
returnMatrix[3] = (matrix4[12] * vector4[0]) + (matrix4[13] * vector4[1]) + (matrix4[14] * vector4[2]) + (matrix4[15] * vector4[3]);
return returnMatrix;
}
@Override
public void onDrawFrame(GL10 gl10) {
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
long time = SystemClock.uptimeMillis() % 10000L;
float angleInDegrees = (360.0f / 10000.0f) * ((int) time);
GLES20.glViewport(0, 0, (int)(width/2), (int)(height/2));
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, 1.5f, 0f, 0f, -5f, 0f, 1f, 0f);
//Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
drawTriangle(triangleVertices);
//Matrix.translateM(mModelMatrix, 0, 1.5f, 0, -1f);
//Matrix.frustumM(mProjectionMatrix, 0, left, right, -1.0f, 1.0f, 1.0f, 10.0f);
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.setLookAtM(viewMatrix, 0, 1.5f, 0.8f, 0.5f, 0f, 0f, 0f, 0f, 1f, 0f);
GLES20.glViewport((int)(width/2), (int)(height/2), (int)(width/2), (int)(height/2));
drawTriangle(triangleVertices);
drawIntersectionLine();
/*
Matrix.setLookAtM(viewMatrix, 0, 0, 1.5f, 0.5f, 0, 0, 0, 0, 0, -1f);
GLES20.glViewport((int)(width/2), (int)height, (int)(width/2), (int)(height/2));
drawTriangle(triangleVertices);
drawIntersectionLine();
*/
}
private void drawTriangle(final FloatBuffer triangleBuffer)
{
triangleBuffer.position(positionOffset);
GLES20.glVertexAttribPointer(mPositionHandle, positionDataSize, GLES20.GL_FLOAT, false, strideBytes, triangleBuffer);
GLES20.glEnableVertexAttribArray(mPositionHandle);
triangleBuffer.position(colorOffset);
GLES20.glVertexAttribPointer(mColorHandle, colorDataSize, GLES20.GL_FLOAT, false, strideBytes, triangleBuffer);
GLES20.glEnableVertexAttribArray(mColorHandle);
Matrix.multiplyMM(mMVPMatrix, 0, viewMatrix, 0, mModelMatrix, 0);
mMVMatrix = mMVPMatrix;
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
//Log.d("OpenGLES2Test", "The intersection ray is: " + floatArrayAsString(getCameraPos(mMVMatrix)) + " + " + floatArrayAsString(getMouseRayProjection((int)(width / 2), (int)(height / 2), 1.0f, (int)width, (int)height, mMVMatrix, mProjectionMatrix)));
}
private void drawIntersectionLine()
{
lineVertices.position(0);
GLES20.glVertexAttribPointer(mPositionHandle, positionDataSize, GLES20.GL_FLOAT, false, lineStrideBytes, lineVertices);
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glDrawArrays(GLES20.GL_LINES, 0, 2);
}
private void moveIntersectionLineEndPoint(float[] lineEndPoint)
{
this.lineEndPoint = lineEndPoint;
float[] lineVerticesData = {
lineStartPoint[0], lineStartPoint[1], lineStartPoint[2],
lineEndPoint[0], lineEndPoint[1], lineEndPoint[2]
};
lineVertices = ByteBuffer.allocateDirect(lineVerticesData.length * bytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
lineVertices.put(lineVerticesData).position(0);
}
Although I'm pretty sure my 4x4 matrix by 4d vector multiplication method is correct, here is that method as well just in case:
public float[] multiplyMat4ByVec4(float[] matrix4, float[] vector4)
{
float[] returnMatrix = new float[4];
returnMatrix[0] = (matrix4[0] * vector4[0]) + (matrix4[1] * vector4[1]) + (matrix4[2] * vector4[2]) + (matrix4[3] * vector4[3]);
returnMatrix[1] = (matrix4[4] * vector4[0]) + (matrix4[5] * vector4[1]) + (matrix4[6] * vector4[2]) + (matrix4[7] * vector4[3]);
returnMatrix[2] = (matrix4[8] * vector4[0]) + (matrix4[9] * vector4[1]) + (matrix4[10] * vector4[2]) + (matrix4[11] * vector4[3]);
returnMatrix[3] = (matrix4[12] * vector4[0]) + (matrix4[13] * vector4[1]) + (matrix4[14] * vector4[2]) + (matrix4[15] * vector4[3]);
return returnMatrix;
}
The goal of this test app is to show the scene from a few separate angles so that I can see how the intersection line looks based on my code. I wanted to draw the line starting at the camera's origin and ending at the intersection point, but it's acting oddly. The endpoint seems to be being pushed farther along the x axis in the positive direction than it should be, and in some spots it's seems to sort of...skip, as if there were a hole at that location or something. Although I still remember a bit of linear algebra from calculus, I don't remember enough to know exactly what I'm doing here and I've scoured through many of the resources online with no luck. I'm hoping someone that reads this will have more experience dealing with this than I and will be kind enough to help me, or give me any tips if there's something else that I may be doing the wrong or in an inefficient way.
Variable Reference: Matrices are all float arrays of length 16
mProjectionMatrix = projection matrix
mModelMatrix = model matrix
mMVPMatrix = projection * modelview matrix
mMVMatrix = modelview matrix
private final FloatBuffer triangleVertices;
private FloatBuffer lineVertices;
private final int bytesPerFloat = 4;
private float[] viewMatrix = new float[16];
private static Context context;
private int mMVPMatrixHandle;
private int mPositionHandle;
private int mColorHandle;
private float[] mProjectionMatrix = new float[16];
private float[] mModelMatrix = new float[16];
private float[] mMVPMatrix = new float[16];
private float[] mMVMatrix = new float[16];
private final int strideBytes = 7 * bytesPerFloat;
private final int lineStrideBytes = 3 * bytesPerFloat;
private final int positionOffset = 0;
private final int positionDataSize = 3;
private final int colorOffset = 3;
private final int colorDataSize = 4;
private float width, height;
private float[] lineStartPoint = new float[]{0, 0, 1.5f};
private float[] lineEndPoint = new float[]{0, 0, 0};
your codes are good but it does not work fine for me. After studying the code from Rajawali ( https://github.com/MasDennis/Rajawali ), I would suggest the following changes in your final codes:
I think the most important part is that you should divide the vector by the W element.
After some searching, I found a page that details this process in a different manner. Now I no longer have the issue with the end of the ray jumping to an unexpected position at random times and the end point points to the exact location it should! Here is the page I used to fix my process: http://www.antongerdelan.net/opengl/raycasting.html
And here is my final source code for the entire intersection testing app. Most of the relevant code is within the OpenGLRenderer class under the getMouseRayProjection method.
MainActivity.java:
OpenGLRenderer.java:
fragmentShader:
vertexShader: