Friday, 1 January 2010

iPhone - Unproject a 2D point

Alright, just added in support for selecting 3d objects using 2d coordinates. So you can select an object in a 3d scene using your finger. The idea to do this is to unproject the 2d coordinates passed in at 0 and 1 range to get the direction of the ray, then simply use that ray to find which objects collide.

Step one was to find the source for glu unProject function. There's loads of sources online for OpenGL, so all you need to do is port it over to OpenGL ES (just replace GLdouble with GLfloat).

Here's some source code ported from http://code.google.com/p/iphone-glu
 static void __gluMultMatrixVecd(const GLfloat matrix[16], const GLfloat in[4], 
GLfloat out[4])
{
int i;
for( i=0; i<4; ++i )
{
out[i] =
in[0] * matrix[0*4+i] +
in[1] * matrix[1*4+i] +
in[2] * matrix[2*4+i] +
in[3] * matrix[3*4+i];
}
}
/*
** Invert 4x4 matrix.
** Contributed by David Moore (See Mesa bug #6748)
*/
static int __gluInvertMatrixd(const GLfloat m[16], GLfloat invOut[16])
{
GLfloat inv[16];
inv[0] = m[5]*m[10]*m[15] - m[5]*m[11]*m[14] - m[9]*m[6]*m[15]
+ m[9]*m[7]*m[14] + m[13]*m[6]*m[11] - m[13]*m[7]*m[10];
inv[4] = - m[4]*m[10]*m[15] + m[4]*m[11]*m[14] + m[8]*m[6]*m[15]
- m[8]*m[7]*m[14] - m[12]*m[6]*m[11] + m[12]*m[7]*m[10];
inv[8] = m[4]*m[9]*m[15] - m[4]*m[11]*m[13] - m[8]*m[5]*m[15]
+ m[8]*m[7]*m[13] + m[12]*m[5]*m[11] - m[12]*m[7]*m[9];
inv[12] = - m[4]*m[9]*m[14] + m[4]*m[10]*m[13] + m[8]*m[5]*m[14]
- m[8]*m[6]*m[13] - m[12]*m[5]*m[10] + m[12]*m[6]*m[9];
inv[1] = - m[1]*m[10]*m[15] + m[1]*m[11]*m[14] + m[9]*m[2]*m[15]
- m[9]*m[3]*m[14] - m[13]*m[2]*m[11] + m[13]*m[3]*m[10];
inv[5] = m[0]*m[10]*m[15] - m[0]*m[11]*m[14] - m[8]*m[2]*m[15]
+ m[8]*m[3]*m[14] + m[12]*m[2]*m[11] - m[12]*m[3]*m[10];
inv[9] = - m[0]*m[9]*m[15] + m[0]*m[11]*m[13] + m[8]*m[1]*m[15]
- m[8]*m[3]*m[13] - m[12]*m[1]*m[11] + m[12]*m[3]*m[9];
inv[13] = m[0]*m[9]*m[14] - m[0]*m[10]*m[13] - m[8]*m[1]*m[14]
+ m[8]*m[2]*m[13] + m[12]*m[1]*m[10] - m[12]*m[2]*m[9];
inv[2] = m[1]*m[6]*m[15] - m[1]*m[7]*m[14] - m[5]*m[2]*m[15]
+ m[5]*m[3]*m[14] + m[13]*m[2]*m[7] - m[13]*m[3]*m[6];
inv[6] = - m[0]*m[6]*m[15] + m[0]*m[7]*m[14] + m[4]*m[2]*m[15]
- m[4]*m[3]*m[14] - m[12]*m[2]*m[7] + m[12]*m[3]*m[6];
inv[10] = m[0]*m[5]*m[15] - m[0]*m[7]*m[13] - m[4]*m[1]*m[15]
+ m[4]*m[3]*m[13] + m[12]*m[1]*m[7] - m[12]*m[3]*m[5];
inv[14] = - m[0]*m[5]*m[14] + m[0]*m[6]*m[13] + m[4]*m[1]*m[14]
- m[4]*m[2]*m[13] - m[12]*m[1]*m[6] + m[12]*m[2]*m[5];
inv[3] = - m[1]*m[6]*m[11] + m[1]*m[7]*m[10] + m[5]*m[2]*m[11]
- m[5]*m[3]*m[10] - m[9]*m[2]*m[7] + m[9]*m[3]*m[6];
inv[7] = m[0]*m[6]*m[11] - m[0]*m[7]*m[10] - m[4]*m[2]*m[11]
+ m[4]*m[3]*m[10] + m[8]*m[2]*m[7] - m[8]*m[3]*m[6];
inv[11] = - m[0]*m[5]*m[11] + m[0]*m[7]*m[9] + m[4]*m[1]*m[11]
- m[4]*m[3]*m[9] - m[8]*m[1]*m[7] + m[8]*m[3]*m[5];
inv[15] = m[0]*m[5]*m[10] - m[0]*m[6]*m[9] - m[4]*m[1]*m[10]
+ m[4]*m[2]*m[9] + m[8]*m[1]*m[6] - m[8]*m[2]*m[5];
GLfloat det = m[0]*inv[0] + m[1]*inv[4] + m[2]*inv[8] + m[3]*inv[12];
if( det == 0 )
{
return GL_FALSE;
}
det = 1.0 / det;
for( int i=0; i<16; ++i )
{
invOut[i] = inv[i] * det;
}
return GL_TRUE;
}
static void __gluMultMatricesd(const GLfloat a[16], const GLfloat b[16],
GLfloat r[16])
{
int i, j;
for( i=0; i<4; ++i )
{
for( j=0; j<4; ++j )
{
r[i*4+j] =
a[i*4+0]*b[0*4+j] +
a[i*4+1]*b[1*4+j] +
a[i*4+2]*b[2*4+j] +
a[i*4+3]*b[3*4+j];
}
}
}
BOOL gluUnProject(GLfloat winx, GLfloat winy, GLfloat winz,
const GLfloat modelMatrix[16],
const GLfloat projMatrix[16],
const GLint viewport[4],
GLfloat *objx, GLfloat *objy, GLfloat *objz)
{
GLfloat finalMatrix[16];
GLfloat in[4];
GLfloat out[4];
__gluMultMatricesd( modelMatrix, projMatrix, finalMatrix );
if( !__gluInvertMatrixd( finalMatrix, finalMatrix ) )
{
return NO;
}
in[0] = winx;
in[1] = winy;
in[2] = winz;
in[3] = 1.0;
/* Map x and y from window coordinates */
in[0] = ( in[0] - viewport[0] ) / viewport[2];
in[1] = ( in[1] - viewport[1] ) / viewport[3];
/* Map to range -1 to 1 */
in[0] = in[0] * 2 - 1;
in[1] = in[1] * 2 - 1;
in[2] = in[2] * 2 - 1;
__gluMultMatrixVecd(finalMatrix, in, out);
if( out[3] == 0.0 )
{
return NO;
}
out[0] /= out[3];
out[1] /= out[3];
out[2] /= out[3];
*objx = out[0];
*objy = out[1];
*objz = out[2];
return YES;
}
Step two take in a 2d coordinate and feed it into the collision detection system. My collision system scans through all base objects of ObjectCollideable and returns the one which is closest to the start of the ray. The viewport and projection matrices are calculated on OpenGL initialisation with the following calls.
 ObjectCollideable* Projection2DScan(CGPoint point)  
{
GLfloat modelViewMatrix[16];
glGetFloatv( GL_MODELVIEW_MATRIX, modelViewMatrix );

point.y = 1.0f - point.x;
point.x *= gView->viewport[2];
point.y *= gView->viewport[3];

Vector3 nearPlane, farPlane;
gluUnProject( point.x, point.y, 0.0f, modelViewMatrix, gView->projectionMatrix, gView->viewport, &nearPlane.x, &nearPlane.y, &nearPlane.z );
gluUnProject( point.x, point.y, 1.0f, modelViewMatrix, gView->projectionMatrix, gView->viewport, &farPlane.x, &farPlane.y, &farPlane.z );

// Figure out our ray's direction
Vector3 direction = farPlane;
Vector3Sub( &direction, &nearPlane );
Vector3Normalize( &direction );

// Cast the ray from our near plane
Vector3 farPoint = Vector3MulResult( &direction, 500.0f );
Vector3Add( &farPoint, &nearPlane );

Vector3 hitLocation;
ObjectCollideable *hitObject = BasicLineCollisionCheck( &nearPlane, &farPoint, &hitLocation, gEngine->collideables );
return hitObject;
}
That's it, your collision system can now use that ray to return the closest hit object.