Thursday, April 28, 2011

PhysX Basics Tutorial: Picking



In this tutorial, we will use the scene of multiple boxes from the last tutorial. This tutorial assumes that you have successfully understood the basics of how to create an actor using the scene object. If not, u may want to review the previous tutorials as a refresher. Ok so now lets get started. There are a lot of things similar between this and the previous tutorial.

General picking
We will first look at how picking works in general. The user picks on a point on the window. The 2D point coordinates are converted to a 3d point. Then, a ray is generated. The origin and direction of the ray are first calculated by unprojecting the clicked point at different depths. The same point coordinates are used but with two depths (z=0 and z=1) for the near hit point and the far hit point respectively. Once we have constructed the ray, the scene is asked to find an intersecting actor. If the ray has intersected with any actor, a dynamic spring is created between the intersected actor and a sphere at the intersection point. In the mouse motion funciton, depending on whether the actor has been intersected the intersected actor is manipulated.

Picking in PhysX
We define two new functions ViewProject and ViewUnProject as follows,

void ViewProject(NxVec3 &v, int &xi, int &yi, float &depth)
{
GLdouble winX, winY, winZ;
gluProject((GLdouble) v.x, (GLdouble) v.y, (GLdouble) v.z, modelMatrix, projMatrix, viewPort, &winX, &winY, &winZ);
xi = (int)winX; yi = viewPort[3] - (int)winY - 1; depth = (float)winZ;
}
void ViewUnProject(int xi, int yi, float depth, NxVec3 &v)
{
yi = viewPort[3] - yi - 1;
GLdouble wx, wy, wz;
gluUnProject((GLdouble) xi, (GLdouble) yi, (GLdouble) depth,
modelMatrix, projMatrix, viewPort, &wx, &wy, &wz);
v.set((NxReal)wx, (NxReal)wy, (NxReal)wz);
}

The above functions simply call the glu[Un]Project function. Nothing fancy here. Both of these functions need the viewport, modelview and projection matrices so we store these matrices globally. We obtain the viewport values and projection matrix when they are setup (in the resize handler) as shown below.

void OnReshape(int nw, int nh) {
glViewport(0,0,nw, nh);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, (GLfloat)nw / (GLfloat)nh, 0.1f, 1000.0f);
glGetIntegerv(GL_VIEWPORT, viewPort);
glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
glMatrixMode(GL_MODELVIEW);
}

Similarly, we obtain the modelview matrix in the render function just after the viewing transformation is set.

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
//setting up modelview matrix
glTranslatef(0,0,dist);
glRotatef(rX,1,0,0);
glRotatef(rY,0,1,0);
glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
...
//rest of display function

So now we have our matrices. We now come on the Pick function. This function takes an x, y screen coordinate, cast a ray from into the scene and returns true if there is an intersection. It also stores a sphere object at the hit point, stores reference to the intersected actor and also store a spring between the hitpoint sphere and the actor. This function is defined as follows,


bool PickActor(int x, int y)
{
ReleaseHelpers();

We first release all previous helpers (the spring and the last intersection sphere.


NxRay ray;
ViewUnProject(x,y,0.0f, ray.orig);
ViewUnProject(x,y,1.0f, ray.dir);
ray.dir -= ray.orig; ray.dir.normalize();

The above lines create a ray as given in the previous paragraph.


NxRaycastHit hit;
NxShape* closestShape = gScene->raycastClosestShape(ray, NX_ALL_SHAPES, hit);
if (!closestShape) return false;
if (!closestShape->getActor().isDynamic()) return false;

The above lines is where the ray is cast to find the closest actor. If there is none, we return false.


int hitx, hity;
ViewProject(hit.worldImpact, hitx, hity, gMouseDepth);
gMouseSphere = CreateSphere(hit.worldImpact, 0.1f, 1.0f);

gMouseSphere->raiseBodyFlag(NX_BF_KINEMATIC);
gMouseSphere->raiseActorFlag(NX_AF_DISABLE_COLLISION);

The above lines project the hit point back to get the world space position. This is done so that the picking cursor (sphere) could be drawn at the intersection point.
The sphere's body flag is raised to kinematic since it will be attached to an actor(see next few lines). The collisions are also disabled since we donot want our cursor to intersect with any geometry.



NxDistanceJointDesc desc;
gSelectedActor = &closestShape->getActor();
gSelectedActor->wakeUp();

desc.actor[0] = gMouseSphere;
desc.actor[1] = gSelectedActor;

These lines create a descriptor for a joint having one end point, our sphere cursor and the other end point our intersected actor.


gMouseSphere->getGlobalPose().multiplyByInverseRT(hit.worldImpact, desc.localAnchor[0]);
gSelectedActor->getGlobalPose().multiplyByInverseRT(hit.worldImpact, desc.localAnchor[1]);

The above lines modify the transformation of the intersected actor based on the hit point.


desc.maxDistance = 0.0f;
desc.minDistance = 0.0f;
desc.spring.damper = 1.0f;
desc.spring.spring = 200.0f;
desc.flags |= NX_DJF_MAX_DISTANCE_ENABLED | NX_DJF_SPRING_ENABLED;
NxJoint* joint = gScene->createJoint(desc);
gMouseJoint = (NxDistanceJoint*)joint->is(NX_JOINT_DISTANCE);
return true;
}

Finally, the descriptor parameters are filled in and a joint is created. Note that on every mouse click, the old cursor (sphere) and joint are released and a new pair is created. The ReleaseHelpers function is defined as follows,

void ReleaseHelpers()
{
if (gMouseJoint)
gScene->releaseJoint(*gMouseJoint);
gMouseJoint = NULL;
if (gMouseSphere)
gScene->releaseActor(*gMouseSphere);
gMouseSphere = NULL;
}

Nothing fancy here either. We just release the created objects (the sphere cursor and the joint).

That's it. You need to call the pick function in the mouse function and handle the drag event. Enjoy this snapshot from the demo.

PhysX Picking

Source code of this tutorial

3 comments:

Unknown said...

Hello,

I am trying to understand/create simple applications with the PhysX SDK, my final goal is one big and complicated clothing application.

I have followed your tutorials until now, and they have been extremely helpful, but I came across a problem that I have absolutely no idea how to solve, and I really don't know where to look for the answer, since their documentation is very abstract, and your code simply doesn't work for me, and I really don't understand why, and I was hoping that you might help me.

I am using PhysX SDK version 3.2.1, and I tried the current tutorial(Picking). I must mention that up until now I have followed your tutorials exactly, and basically have the same code, just the order of the functions differs.

Everything works just fine, except for the raytracing part. When running the program, the scene with the cubes on the plane appears, but when I try to pick one cube, only the plane moves (I believe that the perspective is changed), and in the debugger I get the error: "Invalid parameter: Ray distance not valid: must be greater than zero!". I really don't know what's wrong, I even tried to modify the ray depth to be 100.0f instead of 1.0f. Do you know what might be the problem?

And also, the code concerning the glut functions, did you take them from somwhere or you created them yourself? I don't understand what happens in some of them, like SetOrthoForFont, ResetPerspectiveProjection, RenderSpacedBitmapString, DrawGrid (I know this draws the grid, but I don't really understand how), and also, what is the difference between display and onRender?

I must mention that I am a beginner both in physx and glut, and I am really sorry if some of the questions might be obvious, and I would really appreciate if you could help me.

Thank you,
Corina

Unknown said...

Also, could you please tell me what you used as documentation for the tutorials? Only the documentation provided by the SDK or you used something else as well?

MMMovania said...

Hi Sur Aliad,
I really can't tell much what might be wrong as there are so many reasons. If possible you can share your code with me and I will have a look at it to see why it is not working. Email me on my gmail address (mmmovania at gmail dot com)

I did not use anything other than the PhysX documentation for these tutorials. If there was anything out there, I wouldn't have done my tutorials in the first place :)

Popular Posts

Copyright (C) 2011 - Movania Muhammad Mobeen. Awesome Inc. theme. Powered by Blogger.