Tutorial 104: - Ray Cast And Convex Cast

From Newton Wiki
Jump to: navigation, search

Template:Languages
Although Ray casting has nothing to with Newtonian physics or with collision detection, it is such a useful tool that no physics library is complete, if it does not have ray casting functionality.

Another useful tool that is very, very powerful is the Convex Casting although not as popular that Ray casting. In this tutorial we will demonstrate the usage of these two power tool.

Select project Tutorial_104_RayCastAndConvexCast as Startup project in visual studio and open file CreateScene in the editor. You will find function CreateScene

void CreateScene (NewtonWorld* world, SceneManager* sceneManager)
{
	Entity* floor;
	NewtonBody* floorBody;
	NewtonCollision* shape;

	// Create a large body to be the floor
	floor = sceneManager->CreateEntity();
	floor->LoadMesh ("FloorBox.dat");

	// add static floor physics object
	shape = CreateNewtonBox (world, floor, 0);
	floorBody = CreateRigidBody (world, floor, shape, 0.0f);
	NewtonReleaseCollision (world, shape);

	// set the Transformation Matrix for this rigid body
	dMatrix matrix (floor->m_curRotation, floor->m_curPosition);
	NewtonBodySetMatrix (floorBody, &matrix[0][0]);

	// now we will use the properties of this body to set a proper world size.
	dVector minBox;
	dVector maxBox;
	NewtonCollisionCalculateAABB (shape, &matrix[0][0], &minBox[0], &maxBox[0]);

	// add some extra padding
	minBox.m_x -=  50.0f;
	minBox.m_y -= 500.0f;
	minBox.m_z -=  50.0f;

	maxBox.m_x +=  50.0f;
	maxBox.m_y += 500.0f;
	maxBox.m_z +=  50.0f;

	// set the new world size
	NewtonSetWorldSize (world, &minBox[0], &maxBox[0]);

	// find the floor base, and add some distance up;
	dFloat floorY = FindFloor (world, 0.0f, 0.0f) + 2.0f;

	// this is the width of the Box;
	dFloat boxWidth = 0.42f;

	// Make a small pyramid of Boxes
	for (int i = 0; i < PYRAMID_BASE + 1; i ++) {
		for (int j = 0; j < PYRAMID_BASE - i; j ++) {
			Entity* smilly;
			NewtonBody* smillyBody;

			// crate a visual Box and place in a pyramid formation above the floor
			smilly = sceneManager->CreateEntity();
			smilly->LoadMesh ("Smilly.dat");
			smilly->m_curPosition.m_x = 0.0f;
			smilly->m_curPosition.m_z = dFloat (j) * boxWidth + dFloat (i) * (boxWidth * 0.5f);
			smilly->m_curPosition.m_y = floorY + dFloat (i) * boxWidth;
			smilly->m_prevPosition = smilly->m_curPosition;

			// add a body with a box shape
			shape = CreateNewtonBox (world, smilly, 0);
			smillyBody = CreateRigidBody (world, smilly, shape, 10.0f);
			NewtonReleaseCollision (world, shape);

			// we want some nice object placement, with zero penetration and and zero jitter
			// therefore we are going use use a Convex Cast function to snap the Box to the closest contact surface
			ConvexCastPlacement (smillyBody);
		}
	}
}

Create Scene is very simple function that put a pyramids of boxes over a static large floor. The function is very similar to function Create Scene in Tutorial Create Rigid body, but there is one difference. Here we one to place the objects in and orderly manner.

And argument can be made that because, of the regularity of the scene, the placement of the boxes can be made algorithmically, but if you consider that if you were working on a level editor and you want to place object over a generic surface then the chance the a manual place will create lots of physics artifact the moment the bodies start moving physically. To help with citations like this the Newton engine provides the function Convex Cast.

A Convex Cast it the operation of swiping a convex shapes along a straight line and stopping at the very first intersection with any of the collision shape in the scene. The shape can have and arbitrary orientation, but it does not rotate along the path of the Cast.

You can see a Convex Cast as the generalization of a Ray Cast, A Ray Cast is a Convex Cast that cast a point. However Convex cast are not as efficient as Ray Cast when Casting long lines. It is up to the programmer to decide when to use one or the other. In General the rule of thumb is to use Convex Cast for short path where precision is needed, and Ray Cast for detecting Longer less precise paths.


In the tutorial we create a pyramid starting from the Bottom and moving up, The placement of each box is done by calling function ConvexCastPlacement in file CastFloor.cpp The function looks like this:

void ConvexCastPlacement (NewtonBody* body)
{
	dFloat param;
	dMatrix matrix;
	NewtonWorld* world;
	NewtonCollision* collision;
	NewtonWorldConvexCastReturnInfo info[16];

	// get the Body Matrix;
	NewtonBodyGetMatrix (body, &matrix[0][0]);

	// add some search for a floor surface withe it +20 unit up and -20 units down
	matrix.m_posit.m_y += 20.0f;
	dVector p (matrix.m_posit);
	p.m_y -= 40.0f;

	// cast the collision shape of the body for +20 to -20 units
	world = NewtonBodyGetWorld(body);
	collision = NewtonBodyGetCollision(body);
	NewtonWorldConvexCast (world, &matrix[0][0], &p[0], collision, &param, body, ConvexCastCallback, info, 16, 0);
	_ASSERTE (param < 1.0f);

	// the point at the intersection param is the floor 
	matrix.m_posit.m_y += (p.m_y - matrix.m_posit.m_y) * param;

	// Set the Body matrix to the new placement Matrix adjusted by the cast proccess.
	NewtonBodySetMatrix(body, &matrix[0][0]);
}

The function read the current collision shape and transformation matrix of the rigid and the, cast the shape form 20 unit above the current position to 20 units below the current position. The convex Cast function needs a filter callback where undesired bodies can be rejected from the scan.

For this tutorial the filter call back looks like this:

static unsigned ConvexCastCallback (const NewtonBody* body, const NewtonCollision* collision, void* userData)
{
	// this convex cast have to skip the casting body
	NewtonBody* me = (NewtonBody*) userData;
	return (me == body) ? 0 : 1;
}

Basically it prevents the scan from casting the self rigid body.

After the NetwonWorldConvexcast returns, it will set the parametric value between the origin an target position at witch the scan hit another rigid body. Since the initial orientation of the shape is the same of the rigid body, to have a good placement the only thing we need to do is to translate the body matrix to the hit position and set this new matrix into the rigid body. Then the loop continues building the pyramid until it reached the top.


The second thing different in this tutorial is the user can point click over a body on the screen and grab it to move it around. The implementation of the Mouse pick function is in file MousePick, this function is from the main loop by function InputControls. The implementation of Ray Pick function is very simple.

bool MousePick (NewtonWorld* nWorld, const dVector& cursorPosit1)
{
	static int mouseKey1;
	static int mouseKey0;
	static dVector cursorPosit0;
	static bool mousePickMode = false;

	mouseKey1 = IsKeyDown (KeyCode_L_BUTTON) || IsKeyDown (KeyCode_R_BUTTON);
	if (mouseKey1) {
		if (!mouseKey0) {
			dVector p0 (ScreenToWorld(dVector (cursorPosit1.m_x, cursorPosit1.m_y, 0.0f, 0.0f)));
			dVector p1 (ScreenToWorld(dVector (cursorPosit1.m_x, cursorPosit1.m_y, 1.0f, 0.0f)));

			pickedBody = NULL;
			pickedParam = 1.1f;
			isPickedBodyDynamics = false;
			NewtonWorldRayCast(nWorld, &p0[0], &p1[0], RayCastFilter, NULL, RayCastPrefilter);
			if (pickedBody) {
				dMatrix matrix;
			
				mousePickMode = true;

				NewtonBodyGetMatrix(pickedBody, &matrix[0][0]);
				dVector p (p0 + (p1 - p0).Scale (pickedParam));

				attachmentPoint = matrix.UntransformVector (p);

				// convert normal to local space
				rayLocalNormal = matrix.UnrotateVector(rayLocalNormal);

				// Create PickBody Joint
				bodyPickController = CreateCustomKinematicController (pickedBody, &p[0]);
				if (IsKeyDown (KeyCode_R_BUTTON)) {
					CustomKinematicControllerSetPickMode (bodyPickController, 1);
					CustomKinematicControllerSetMaxAngularFriction (bodyPickController, MAX_FRICTION_ANGULAR_GRAVITY);
					CustomKinematicControllerSetMaxLinearFriction (bodyPickController, MAX_FRICTION_LINEAR_ACCELERATION);
				} else {
					CustomKinematicControllerSetPickMode (bodyPickController, 0);
					CustomKinematicControllerSetMaxAngularFriction (bodyPickController, 10.0f);
					CustomKinematicControllerSetMaxLinearFriction (bodyPickController, MAX_FRICTION_LINEAR_ACCELERATION);
				}
			}
		}

		if (mousePickMode) {
			// init pick mode
			dVector p0 (ScreenToWorld(dVector (cursorPosit1.m_x, cursorPosit1.m_y, 0.0f, 0.0f)));
			dVector p1 (ScreenToWorld(dVector (cursorPosit1.m_x, cursorPosit1.m_y, 1.0f, 0.0f)));
			dVector p (p0 + (p1 - p0).Scale (pickedParam));
			if (bodyPickController) {
				CustomKinematicControllerSetTargetPosit (bodyPickController, &p[0]);
			}
		}
	} else {
		mousePickMode = false;
		if (pickedBody) {
			if (bodyPickController) {
				CustomDestroyJoint (bodyPickController);
			}
			bodyPickController = NULL;
		}
	}

	cursorPosit0 = cursorPosit1;
	mouseKey0 = mouseKey1;

	bool retState;
	retState = isPickedBodyDynamics;
	return retState;
}

Basically it is a state machine with three stages. No Picking, Picked, and Release.

Not Picking is when the user has not depressed any mouse bottom, and no action happens.

Picking a body is when the user points the mouse cursor over a rigid body and holds down the left or right mouse bottom. When this happen the function use the Ray cast functionality to see if a ray starting from the mouse cursor hit a dynamics body.

The ray is create by taken a point on the view port at the front clipping plane and the same location in the viewport but at the far clipping plane. These two points are inverse transformed by the Projection matrix, and the View model matrix into the global space, and ray is shut by calling function NewtonWorldRaycast. The filter function for this Ray cast look like this:

// implement a ray cast filter
static dFloat RayCastFilter (const NewtonBody* body, const dFloat* normal, int collisionID, void* userData, dFloat intersetParam)
{
	dFloat mass;
	dFloat Ixx;
	dFloat Iyy;
	dFloat Izz;

	NewtonBodyGetMassMatrix (body, &mass, &Ixx, &Iyy, &Izz);
	if (intersetParam <  pickedParam) {
		isPickedBodyDynamics = (mass > 0.0f);
		pickedParam = intersetParam;
		pickedBody = (NewtonBody*)body;
		rayLocalNormal = dVector (normal[0], normal[1], normal[2]);
	}
	return intersetParam;
}

It checks if the current hit parameter is closer than the previous, if it is and the body mass in not zero this means that this is a potential hit body.

The function Return the Hit parameter to tell Newton that is does not need to cast any other object than is farther away that the current one. This is a very important thing to remember, when the application is casting the closest object, if the application 1.0 Newton will not shorten the Ray and will continues scanning for more bodies along the ray. This is not bad nor it is good, it depends on what the application wants to do, if the application wants a first hit body then retuning the hit parameter is good, if the application want and all body that the ray can intersect, then returning 1.0 is the correct way. If the application returns zero casting it will return immediately, but the hit body may not be the closest to the ray origin.


If the ray hit a rigid body, function MousePick collect the information: the parametric values, the hit body, the hit point and normal. Then is uses that information to create CustomKinematicController, (you can check out the Tutorial_103_using Joint to see how this joint is used)

Using a kinematic joint simplified the implementation of this feature by a great deal since the code do not have to keep track of updating the forces and torques to grab teh body, plus it also offers different flavors of body picking, for example we implement two pick modes, pick by a single point, and pick by fixing body; but the application can implement more variations like pick and rotate.

The third stage is when the user releases the mouse bottom, when this happens the function simple destroy the CustomKinematicController and set to NULL the pointer to body, to get ready for the next point and click operation.


With this we complete this tutorial. We implemented to basic tools: Raycast and Placement Cast. They in my opinion are fundamental part of any graphics application. We will use these tools with newer tutorials, plus we will also implement deferent version of Raycast and ConvexCast for different purpose, but the philosophy for using these function is always the same as we show here.