Panda PhysX

Ooo! I’ll give compiling it a try.

OK, done, checked in the changes I needed to make. The latest OSX buildbot build should contain the PhysX support. (intel only, no ppc)

I haven’t done extensive testing, but basic initialisation seems to work.

I tested it some, and it seems to work for me as well.

I took a pass at this, and checked in a fix. The PhysX samples now work fine for me on 64-bits Linux using the 2.8.3.3 SDK.

Hay enn0x,

I’m getting the following error after downloading a 2 day old buildbot Windows build:

I have the latest System Software installed and SDK 2.8.4.4 on Windows 7 64bit.

Hey executor,

this is a bit strange, since it works for me. Where did you place the following files:

  • PhysXLoader.dll
  • PhysXCore.dll
  • PhysXCooking.dll
  • PhysXDevice.dll
  • cudart32_30_9.dll
  • NxCharacter.dll

I moved all those files to my games folder, where I already had the NxCharacter.dll, and it worked.

Didn’t know they needed to be there?

Starting with PhysX SDK 2.8.4 there is a new deployment pattern. Have a look at the first pages of the PhysX SDK documentation, where the new deployment is described. For example we will be able to distribute games WITHOUT the need to have a system software installed. This is great news, because for example can now have all the above libraries inside Panda3D PhysxX runtime module.

I’ve just recently gotten back into coding after quite a long hiatus. I’ve had to install everything fresh, so at the moment I’m running Panda 1.7.1 with PhysX SDK 2.8.4.5. PhysX DLLs are copied into the bin, but after pulling down my code and try to run I get the following error:

:physx(error): INVALID_PARAMETER: Supplied NxActorDesc::checkValid() returned 1. createActor returns NULL.

Assertion failed: actorPtr at line 396 of c:\buildslave\release_sdk_win32\build\panda3d\panda\src\physx\physxScene.cxx
Traceback (most recent call last):
self.mBody=base.mPhysicsMgr.getScene().createActor(actorDesc)

I understand what this error means, but not why I’m getting it. Here’s the block of code where the error occurs:

        
shapeDesc=PhysxConvexShapeDesc()
shapeDesc.setMesh(PhysxMeshPool.loadConvexMesh(collision))
        
# Set up body description for PhysX
bodyDesc=PhysxBodyDesc()
bodyDesc.setMass(self.mMass)
bodyDesc.setAngularDamping(self.mAngularDamping)
bodyDesc.setLinearDamping(self.mLinearDamping)
bodyDesc.setFlag(PhysxEnums.BFFrozenPosZ, True) # Limit movement the X/Y plane

# Set up actor description for PhysX
actorDesc=PhysxActorDesc()
actorDesc.setBody(bodyDesc)
actorDesc.addShape(shapeDesc)
actorDesc.setGlobalPos(model.getPos())
    
# Create the body as defined by the physics entity
self.mBody=base.mPhysicsMgr.getScene().createActor(actorDesc)

As far as I recall, everything was working when I last tried to run (which was some time ago). I just wanted to check whether there were any changes to Panda/PhysX’s API recently that might have broken this code before I tear it apart.

Difficult. I don’t see any obvious errors in your setup, and actorDesc.isValid() seems to return true.

My best guess is a version mismatch. You are using dlls from the PhytsX SDK 2.8.4.5 (released 16. Mar 2011) while Panda3d 1.7.1 uses the PhysX SDK 2.8.4.4.

Can you run the samples for PhysX using the 2.8.4.5 dlls? (http://enn0x.p3dp.com/samples.zip)

A second guess would be that the PhysX-native format for saving cooked meshes has changed between the releases, and you need to cook them again. But I don’t think this is the problem.

Your instincts were correct. The samples wouldn’t run, so I installed 2.8.4.4 and it fixed all of my problems. Thanks for your help, you’ve saved me hours of confusion!

Can someone post a .blend file of a example level that has like 4 walls and a floor with a hill or something in it? I can’t figure out how to make a level with the cook stuff, all my things seem to keep lacking whatever is needed to label the collision.

Seen like you have problems with cooking, and not Blender. Would make sense if you post a blend file and we show you how to cook.

Guess I should start with a simple thing first.

I used the cook.py file that came with the samples so I thought it parsed the nodes and I just didn’t have the right nodes or something.

Ok, first thing I notices is that the .egg file and the .blend file don’t match: the .blend file contains a “quad” and a “box”, while the .egg file contains no box, but a slighty more sophisticated terrain with one “hill” and one “ditch”.

I tested with both versions, and the cook.py script produced exactly what I was expecting. What might have puzzled you is that the small script doesn’t consider the GeomNode’s transforms - it just reads the vertex data, and thus you get a 1x1 quad and a box at (0,0,0).

Well, the script is just an example for how to use the cooking API. It’s easy to modify the script though:

def processNP(modelNP):
  vertices = []
  triangles = []

  for geomNP in modelNP.findAllMatches('**/+GeomNode'):
    geomNode = geomNP.node()
    ts = geomNode.getTransform()
    for geom in geomNode.getGeoms():
      processVertices(geom, vertices, ts)
      processIndices(geom, triangles)

  return vertices, triangles

def processVertices(geom, vertices, ts):
  m = ts.getMat().getUpper3()
  p = ts.getMat().getRow3(3)

  vdata = geom.getVertexData()
  reader = GeomVertexReader(vdata, 'vertex')
  while not reader.isAtEnd():
    v = reader.getData3f()
    v = m.xform(v) + p
    vertices.append(Point3(v))

One sidenote: The simple cook.py script takes ALL the geometry within or below a NodePath, no matter how many GeomNodes they are. If you want to cook more complex levels you should break the levels down into more-or-less convex chunks. For example a bridge or arc over a valey in your terrain should be broken down into the terrain and the bridge (that is, one .nxb file for the terrain and one for the bridge). Also you might want to filter intangible objects within the script - it’s up to you how you mark them in you egg files (or Blender).

I guess you’re right it was more of a cooking issue. My next question is related to the bridge example you had.

Say I had a model of a house with a window (hole) in one side, would that need to be split up so that the roof that forms the top part of the window would be a separate nxb file?

Can’t say for sure. The algorithms for optimization of collision meshes used by PhysX are closed source, so I don’t know exactly how it behaves.

You could start cooking in one piece, and keep in mind that if relevant “holes” are optimized away you have to split up into smaller pieces.

Yeah the potential removal of the ‘holes’ due to optimization was what I was asking, thought maybe someone might have ran into it already and could save me the trial and error, but with what you’ve told me I should be able to make some decent progress this weekend with converting all my stuff over to using physx instead of the ODE.

Any ideas why this code would crash?

from direct.directbase import DirectStart
from direct.showbase.Loader import Loader
from panda3d.core import Filename
from panda3d.core import Point3, Vec3
from panda3d.physx import PhysxActorDesc
from panda3d.physx import PhysxMeshPool
from panda3d.physx import PhysxTriangleMeshShapeDesc

class PhysicalObject(object):
	def __init__(self, scene, name):
		self.scene = scene

		index = str(name).rfind('/')
		if index != -1:
			self.name = name[index+1:len(name)]
			fileFolder = name[0:index]

		self.modelFile = name + ".egg"
		self.meshFile = name + ".nxb"

		self.model = None
		self.modelNodeRoot = None
		self.actor = None

	def destroy(self, unloadModel=False):
		if self.actor != None:
			self.actor.detachNodePath()
		if self.modelNodeRoot != None:
			self.modelNodeRoot.remove()
		if self.model != None:
			if unloadModel:
				loader.unloadModel(self.model)
			self.model.removeNode()
		if self.actor != None:
			self.actor.release()

		self.model = None
		self.actor = None

	def createActorDesc(self, pos=Point3(0,0,0)):
		actorDesc = PhysxActorDesc()

		# add the mesh shape if there is one
		if self.meshFile != None:
			mesh = PhysxMeshPool.loadTriangleMesh(Filename(self.meshFile))
			shapeDesc = PhysxTriangleMeshShapeDesc()
			shapeDesc.setMesh(mesh)
			actorDesc.addShape(shapeDesc)

		actorDesc.setName(self.name)
		actorDesc.setGlobalPos(pos)

		return actorDesc

	def createActor(self, actorDesc):
		if self.modelFile == None:
			log.error("No model file found")

		self.model = loader.loadModel(self.modelFile)
		self.model.reparentTo(render)

		self.actor = self.scene.createActor(actorDesc)
		self.actor.attachNodePath(self.model)

		self.modelNodeRoot = self.model.find("-PandaNode")

"""
"""

base.setBackgroundColor(.2, .2, .2)
base.camLens.setFov(75)
base.camLens.setNear(0.01)
base.disableMouse()

base.cam.setPos(0, -20, 4)
base.cam.lookAt(0, 0, 1)

from panda3d.physx import PhysxEnums, PhysxManager, PhysxSceneDesc

# Scene
sceneDesc = PhysxSceneDesc()
sceneDesc.setGravity(Vec3(0, 0, -9.81))
sceneDesc.setFlag(PhysxEnums.SFForceConeFriction, True)

scene = PhysxManager.getGlobalPtr().createScene(sceneDesc)

myobject = PhysicalObject(scene, './Assets/Graphics/Models/Maps/testWorld')

actorDesc = myobject.createActorDesc()
myobject.createActor(actorDesc)

I narrowed it down to the line:

self.actor = self.scene.createActor(actorDesc)

It doesn’t seem to crash if the actorDesc is created in that function, or if it is created in the main file instead of using the object to make its own. I can’t figure out why this is happening though. Makes it harder to reuse code with this crash as I have to basically copy paste a bunch of the same actorDesc code in any other object types i make especially if they need a body or shapes added

In case anyone is wondering I figured out the issue with my code inside the createActorDesc I’m making a PhysxTriangleMeshShapeDesc() object but it appears the addShape call to stick it in the actor description doesn’t make a copy of it, it just points to it, so when the code leaves that function it cleans up that object and now it’s pointing at garbage when it tries to create the actor

So basically all I did was make a simple object to temporarily store that shape until after the actor is created then allow it to be cleaned up as usual.

Like this:

from direct.directbase import DirectStart
from direct.showbase.Loader import Loader
from panda3d.core import Filename
from panda3d.core import Point3, Vec3
from panda3d.physx import PhysxActorDesc
from panda3d.physx import PhysxMeshPool
from panda3d.physx import PhysxTriangleMeshShapeDesc

class PhysicalObject(object):
	def __init__(self, scene, name):
		self.scene = scene

		index = str(name).rfind('/')
		if index != -1:
			self.name = name[index+1:len(name)]
			fileFolder = name[0:index]

		self.modelFile = name + ".egg"
		self.meshFile = name + ".nxb"

		self.model = None
		self.modelNodeRoot = None
		self.actor = None

	def destroy(self, unloadModel=False):
		if self.actor != None:
			self.actor.detachNodePath()
		if self.modelNodeRoot != None:
			self.modelNodeRoot.remove()
		if self.model != None:
			if unloadModel:
				loader.unloadModel(self.model)
			self.model.removeNode()
		if self.actor != None:
			self.actor.release()

		self.model = None
		self.actor = None

	def createActorDesc(self, pos=Point3(0,0,0)):
		desc = Description()

		actorDesc = PhysxActorDesc()

		# add the mesh shape if there is one
		if self.meshFile != None:
			mesh = PhysxMeshPool.loadTriangleMesh(Filename(self.meshFile))
			shapeDesc = PhysxTriangleMeshShapeDesc()
			shapeDesc.setMesh(mesh)
			desc.shapeDesc = shapeDesc
			actorDesc.addShape(shapeDesc)

		actorDesc.setName(self.name)
		actorDesc.setGlobalPos(pos)

		desc.actorDesc = actorDesc
		return desc

	def createActor(self, actorDesc):
		if self.modelFile == None:
			print "No model file found"
		else:
			self.model = loader.loadModel(self.modelFile)
			self.model.reparentTo(render)

		self.actor = self.scene.createActor(actorDesc)

		if self.model != None:
			self.actor.attachNodePath(self.model)
			self.modelNodeRoot = self.model.find("-PandaNode")

"""
"""

class Description():
	def __init__(self):
		self.actorDesc = None
		self.shapeDesc = None

base.setBackgroundColor(.2, .2, .2)
base.camLens.setFov(75)
base.camLens.setNear(0.01)
base.disableMouse()

base.cam.setPos(0, -20, 4)
base.cam.lookAt(0, 0, 1)

from panda3d.physx import PhysxEnums, PhysxManager, PhysxSceneDesc

# Scene
sceneDesc = PhysxSceneDesc()
sceneDesc.setGravity(Vec3(0, 0, -9.81))
sceneDesc.setFlag(PhysxEnums.SFForceConeFriction, True)

scene = PhysxManager.getGlobalPtr().createScene(sceneDesc)

myobject = PhysicalObject(scene, './models/testWorld')

desc = myobject.createActorDesc()
myobject.createActor(desc.actorDesc)

All i did was add that Description class to hold both so the shape can’t be cleaned up until I’m done with it