Game Development Community

PhysX Implementation Questions

by Ronald J Nelson · in Torque Game Engine Advanced · 10/19/2008 (11:03 pm) · 66 replies

I have implemented PhysX into my code. With that I have some questions about some things since I have noticed no one has been updating the stuff on TDN.

1. Has anyone gotten it to work with Polysoup, since Polysoup is stock with TGEA 1.7.1 I figured someone might have by now.

2. Has anyone gotten vehicle collisions to work properly with it yet?

3. Has anyone gotten a working example of using links that they are willing to share?

Thanks in advance.
#41
10/26/2008 (8:35 pm)
@Ron, well in what way are you attempting to test the collision? If you're just trying collide things against the static, that's not going to work out real well. Have you verified the actor is showing up in the PhysX Remote Debugger?
#42
10/26/2008 (9:13 pm)
Its the oddest thing it says its there in the scene browser, but you can't see it in the window like you can other PhysX objects. It is behaving as if something wasn't setup to add it all of the way into the world.
#43
10/26/2008 (9:33 pm)
Did you look at its position in the scene browser? Most probably it's at some crazy position. Take a look at your points when you step through the code and see where they're at.
#44
10/26/2008 (9:43 pm)
I stand corrected. It is not there. I was not aware that the terrain is also a triangle mesh.
#45
10/26/2008 (9:46 pm)
So did it succeed and return you an actor or not? What happened when you stepped through the code? Did you get a valid actor (valid memory address etc.)? If it's succeeding the cooking and returning you a valid actor, it's there.
#46
10/26/2008 (9:54 pm)
It did not add an actor, but it cooked the object, because the status bool test passed.

Now I tried this, but it isn't right either because the collisions are still not working but you can at least see a box in the remote debugger.
if (status)
		{
			Con::errorf(ConsoleLogEntry::General, "Cooking Succesful");
			MemoryReadBuffer readBuffer(buf.data);
			NxTriangleMesh* staticMesh = PxWorld->createTriangleMesh(readBuffer);
			
			// Push this actor back into our actor list.
			mTriangleMeshActors.push_back( staticMesh );
			NxTriangleMeshShapeDesc staticMeshDesc;
			staticMeshDesc.meshData = staticMesh;
			actorDesc.shapes.push_back(&staticMeshDesc);
		}
		else
			Con::errorf(ConsoleLogEntry::General, "Cooking Failed");
	}
	NxCloseCooking();
	actorDesc.body = NULL;	
	
	gTSStatic = PxWorld->AddActor(actorDesc);
	if(gTSStatic->active)
	{
		
		gTSStatic->actor->userData = (void*) static_cast<SimObject*>( this );
	}
}

This is giving me the impression there is no actor being added as it was. I am sure my implementation is still not quite right, but it did at least show something.
#47
10/26/2008 (10:02 pm)
You don't need to do that extra stuff. So what you're saying is that when it gets in here:

if (status)
		{
			Con::errorf(ConsoleLogEntry::General, "Cooking Succesful");
			MemoryReadBuffer readBuffer(buf.data);
			[b]NxTriangleMesh* staticMesh[/b] = PxWorld->createTriangleMesh(readBuffer);
			
			// Push this actor back into our actor list.
			mTriangleMeshActors.push_back( staticMesh );
                        /* [b]This stuff is frivolous!  
                        You don't care about the descriptions after you've created the actor.  
                        Why are you holding onto them?[/b]
			NxTriangleMeshShapeDesc staticMeshDesc;
			staticMeshDesc.meshData = staticMesh;
			actorDesc.shapes.push_back(&staticMeshDesc);
                        */
		}

And it calls createTriangleMesh, it doesn't return *anything*? What is the value of "staticMesh" (in bold)? If it returns a valid actor, then it's "added".
#48
10/26/2008 (10:09 pm)
I'm not disagreeing with you, and as for the reason it looks like I am holding on to those is because I copied the method used for terrains and atlas.

if (status)
			{
				NxTriangleMesh* thismesh;
				MemoryReadBuffer readBuffer(buf.data);
				thismesh = PxWorld->createTriangleMesh(readBuffer);

				NxTriangleMeshShapeDesc terrainShapeDesc;
				terrainShapeDesc.meshData				= thismesh;
				//terrainShapeDesc.materialIndex			= terrMatBlock->id;
				actorDesc.shapes.pushBack(&terrainShapeDesc);
			}
			else
			{
				Con::errorf("failed to cook");
			}


			actorDesc.body = NULL;
			gTerrain = PxWorld->AddActor(actorDesc);

			if (gTerrain->active)
			{
				//Con::errorf("***PHYSX*** - Atlas Chunk added with %i vertices and %i faces", nbFaces*3, nbFaces);
				gTerrain->actor->setGlobalOrientationQuat(quat);
				gTerrain->actor->setGlobalPosition( NxVec3(placePos) );
				gTerrain->actor->userData = (void*) static_cast<SimObject*>( this );
			}
			else
			{
				Con::errorf("***PHYSX*** - FAILED on terrain piece");
			}

As you can see they did it the same way. That is the only reason I considered it as a possibility.

As for staticMesh it is a mesh and it is there when I did an if test to see if it existed. My theory here is that since I have it set up as a replacement function for PhysXTSStatic::SetupCollision, that the actor must be added as it was in that function and somehow the triangle mesh attached to it.

I think that is why what I tried above had results in the remote debugger. I didn't say correct results, just results because again, there is no collision. I will ask a retarded question here in regards to that, do you have to export the mesh any differently from 3DS MAx?
#49
10/26/2008 (10:26 pm)
Regardless of that, what about my other questions? Did you step through the code and see that it's returning a valid actor (i.e., is "staticMesh" a valid pointer, not NULL, not freed or otherwise screwed up)? Try sticking this line after you do createTriangleMesh:

Con::printf( "TSStatic actorname: %s, and position, %g, %g, %g", staticMesh->getName(), staticMesh->getGlobalPos().x, staticMesh->getGlobalPos().y, staticMesh->getGlobalPos().z );

You may need to change that to getGlobalPosition or whatever the actual function is called.

Also, don't use Con::errorf for normal output stuff, it will just make it harder to find real errors. Instead use Con::printf.

And if you haven't actually stepped through the code, do so!
#50
10/26/2008 (11:02 pm)
Sorry but none of those functions are part of the NxTriangleMesh class.

I have stepped through the code and everything looks fine. However I ran the two following checks and it provided something that might apply.

Con::printf("staticMesh is being used by '%d' objects", staticMesh->getReferenceCount());
			
			if(staticMesh != NULL)
				Con::printf("staticMesh != NULL");

It is not NULL as the test has proven when I ran it. But the other test is supposed to tell how many objects are actually using the triangle mesh. It said 0. I'm just taking a wild shot in the dark here, but it seems to me that we are successfully creating the triangle mesh but it is not being tied to the tsStatic mesh properly.

Just throwing out an idea there before I have to hit the hay.
#51
10/26/2008 (11:32 pm)
It doesn't need to be tied to anything. If it created it, it should be in the scene. In any case, that code is correct if it's cooking the mesh and returning you a valid actor. Dunno what else to tell you, guess you'll just have to figure it out from here.
#52
10/26/2008 (11:43 pm)
Well I am going to have to disagree with you on a couple of things we have covered so far. The actor code and flag was necessary. I know this because I read through both atlas and terrain creation and based my little changes on those. It worked!!

I really can't thank you enough for all of the help you gave me. For those interested this is the working function.

void PhysXTSStatic::SetupTriangleCollision(bool server, TSStatic &tsstatic)
{
	mServer = server;

	PhysXWorld *PxWorld = PhysXWorld::getWorld(server);

	// if we don't have a PhysXWorld, just return.
	if ( !PxWorld ) 
	{
		return; 
	}

	Box3F box = tsstatic.getObjBox();
	VectorF scale = tsstatic.getScale();//*10.0;
	box.min.convolve(scale);
	box.max.convolve(scale);
	MatrixF wmat = tsstatic.getWorldTransform();
	Point3F placePos = tsstatic.getPosition();
	// make our matrices into Quats... for the physx engine
	QuatF q(wmat);
	Point3F wpos;
	wmat.getColumn(3,&wpos);
	NxQuat quat;
	quat. setXYZW(q.x, q.y, q.z, q.w);
	NxActorDesc actorDesc;
	
	Vector<U32> indicesList;

	TSShapeInstance *shapeInst = tsstatic.mShapeInstance; 

	if ( !shapeInst )
	{
		return;
	}

	NxInitCooking();

	// Loop through each *MeshObjectInstance* in the TSShapeInstance.
	for( U32 i = 0; i < shapeInst->mMeshObjects.size(); i++ )
   	{
		// Grab out the MeshObjectInstance.
		TSShapeInstance::MeshObjectInstance *meshInst = &shapeInst->mMeshObjects[i];

		// Grab out the TSMesh for that MeshObjectInstance.
		TSMesh *mesh = meshInst->getMesh( 0 );

		if ( !mesh )
		{
			continue; // Nothing left to do if no mesh.
		}
		
		// Figure out how many triangles we have...
		U32 numTriangles = 0;
		const U32 base = 0;

		// Loop through the TSDrawPrimitives.	
		for ( U32 j = 0; j < mesh->primitives.size(); j++ )
		{
			// Pull out the TSDrawPrimitive.
			TSDrawPrimitive &draw = mesh->primitives[j];
			U32 start = draw.start;
         
			if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles)
			{
				numTriangles += draw.numElements / 3;
			}
			else
			{
				U32 idx0 = base + mesh->indices[start + 0];
				U32 idx1;
				U32 idx2 = base + mesh->indices[start + 1];
				U32 * nextIdx = &idx1;
				for ( S32 k = 2; k < draw.numElements; k++ )
				{
					*nextIdx = idx2;
					nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1);
					idx2 = base + mesh->indices[start + k];
					if (idx0 == idx1 || idx0 == idx2 || idx1 == idx2)
						continue; 
					numTriangles++;  
				}
			}
		}


		for ( U32 j = 0; j < mesh->primitives.size(); j++ )
		{
			TSDrawPrimitive &draw = mesh->primitives[j];
			
			U32 start = draw.start;
			
			if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles)
			{
				for ( S32 j = 0; j < draw.numElements; )
				{
					U32 idx0 = base + mesh->indices[start + j + 0];
					U32 idx1 = base + mesh->indices[start + j + 1];
					U32 idx2 = base + mesh->indices[start + j + 2];
					indicesList.push_back( idx0 );
					indicesList.push_back( idx1 );
					indicesList.push_back( idx2 );
					
					j += 3;
				}
			}
			else
			{
				U32 idx0 = base + mesh->indices[start + 0];
				U32 idx1;
				U32 idx2 = base + mesh->indices[start + 1];
				U32 * nextIdx = &idx1;
				for (S32 j=2; j<draw.numElements; j++)
				{
					*nextIdx = idx2;
					
					nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1);
					idx2 = base + mesh->indices[start + j];
					if (idx0 == idx1 || idx0 == idx2 || idx1 == idx2)
						continue;

					indicesList.push_back( idx0 );
					indicesList.push_back( idx1 );
					indicesList.push_back( idx2 );
				}
			}
		}
	
		// Here you can loop through your
		// indicesList and index mesh->verts
		// to collect them all.
		int numVertices = mesh->verts.size();
		NxVec3 * points = new NxVec3[numVertices];
	
		// Load vertices
		for(U32 i=0; i < numVertices; i++)
		{
			points[i].x = mesh->verts[i].x;
			points[i].y = mesh->verts[i].y;
			points[i].z = mesh->verts[i].z;
		}
		NxTriangleMeshDesc triangleDesc;
		triangleDesc.numVertices            = numVertices;
		triangleDesc.numTriangles           = numTriangles;
		triangleDesc.pointStrideBytes       = sizeof(NxVec3);
		triangleDesc.triangleStrideBytes	= 3*sizeof(NxU32);
		triangleDesc.points			        = points;
		triangleDesc.triangles              = indicesList.address();
		triangleDesc.flags		     	    = NX_MF_FLIPNORMALS;
		
		// cook it
		MemoryWriteBuffer buf;
		NxCookingParams params;
		params.targetPlatform = PLATFORM_PC;
		params.skinWidth=0.0f;
		params.hintCollisionSpeed = false;
		NxSetCookingParams(params);
		bool status = NxCookTriangleMesh(triangleDesc, buf);
		
		if (status)
		{
			Con::printf("Cooking Succesful");
			MemoryReadBuffer readBuffer(buf.data);
			NxTriangleMesh* staticMesh = PxWorld->createTriangleMesh(readBuffer);
			
			// Push this actor back into our actor list.
			mTriangleMeshActors.push_back( staticMesh );
			NxTriangleMeshShapeDesc staticMeshDesc;
			staticMeshDesc.meshData = staticMesh;
			actorDesc.shapes.push_back(&staticMeshDesc);
		}
		else
			Con::printf("Cooking Failed");
	}
	NxCloseCooking();

	actorDesc.body = NULL;
	gTSStatic = PxWorld->AddActor(actorDesc);
	if(gTSStatic->active)
	{
		gTSStatic->actor->setGlobalOrientationQuat(quat);
		gTSStatic->actor->setGlobalPosition( NxVec3(placePos) );
		gTSStatic->actor->userData = (void*) static_cast<SimObject*>( this );
	}
}

Everything works now the remote debugger and collisions. Thanks again Ross.
#53
10/27/2008 (1:15 am)
I think I ran into the backwards normals issue before. Glad you figured it out :)
#54
10/27/2008 (9:52 am)
One final question before I dive in, have you managed to get PhysX Cloth working in TGEA? I figure in most ways the process will be similar to the one above in terms of cooking the cloth mesh. I just wanted to know if it had been done so I know I am not traveling down uncharted territory.
#55
10/27/2008 (10:08 am)
Ouch I found one failure with my Mesh Cooking Implementation. Players and Vehicles have collision with the Triangle Mesh. PhysX Objects do, but not the original stuff.

I am sure this is probably something that just has to be introduced to the two classes, but I need to know where to start.
#56
10/27/2008 (11:52 am)
Nay, never bothered with cloth. As to your other issue, you'll just have to figure out what kind of actors the player and vehicles are using and look in the documentation in terms of why or why not those types will or won't collide with eachother. It's all in there in the docs, just read up and you should be able to make the necessary changes pretty easily.
#57
10/27/2008 (1:34 pm)
Thanks again Ross. I have both vehicle and Player class using NxCapsuleShapeDesc. I already tried having one as aNxBoxShapeDescand it made no difference .

The odd thing is in the remote debugger, all objects are registering the contacts with each other. The PhysX Actors are not passing through each other in the remote debugger as they are in the actual game.

Just throwing a guess out there, but I'll bet the simulated actors attached to the player and vehicle have no actual effect on them in a collision. Oh sure they work fine against what I like to call "pure" actors, but other than that they pretty much do nothing.
#58
10/27/2008 (5:57 pm)
I think I found exactly what they are behaving like but I do not see why. It seems as if the BodyDesc flag NX_BF_KINEMATIC is being set. I do not see anywhere in the code that it has been done but the behavior is identical.

Quote:
Enables kinematic mode for the actor. Enable kinematic mode for the body.
Kinematic actors are special dynamic actors that are not influenced by forces (such as gravity), and have no momentum. They are considered to have infinite mass and can be moved around the world using the moveGlobal*() methods. They will push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects.

Kinematic actors are great for moving platforms or characters, where direct motion control is desired.

You can not connect Reduced joints to kinematic actors. Lagrange joints work ok if the platform is moving with a relatively low, uniform velocity.

It sounds exactly like my problem. But how?
#59
10/27/2008 (6:35 pm)
Now this one is pissing me off.

Whatever Actor was created in the resource function AddShapeBase really hates being treated as one. You cannot run any of the functions or set anything that would normally be possible for an Actor. As far as I can tell the issue actually lies within the reource code that creates the Actors for Shapebase objects, but I have no idea where, yet.
#60
10/27/2008 (6:40 pm)
@Ron, well, the player being a kinematic actor (in PhysX parlance) is actually the correct setting. You want the actor to be updated by the player's transform. The reason this is giving you problems, though, is that you need to detect collisions against things and move the player back in those cases. You can do this by doing a PhysX sweep test, and scaling the player's position by the "time" value returned (will be called "t" or something to that effect, go look up the sweep test documentation).

This occurs because the kinematic actors don't care about collisions. They are given a position/orientation and just obey that, nothing else. So your player is moving merrily along, right through your TSStatic or whatever, and the kinematic is set to that new position.