The Panda 1.7.0 distribution comes with a Python exposure for PhysX, which is called Panda PhysX. Panda PhysX is a Python library you will use to access the Physx API from a Panda script. This tutorial is created by Claudio Pedica (you may get in touch with him if there are questions)
from panda3d.physx import PhysxManager from panda3d.physx import PhysxEnums from panda3d.physx import PhysxSceneDesc from panda3d.physx import PhysxBodyDesc from panda3d.physx import PhysxActorDesc from panda3d.physx import PhysxBoxShapeDesc from panda3d.physx import PhysxPlaneShapeDesc from panda3d.physx import PhysxPointOnLineJointDesc
self.physx = PhysxManager.getGlobalPtr()
The physics simulation take place into a PhysX scene, a sort of invisible side world where the objects have their physical incarnation. So at first, you really want to create a PhysX Scene. Every time you have to create a general PhysX object, you need to provide a descriptor first and pass it as a parameter to a specific creation method. A descriptor is a kind of blueprint of the object that a creation method will use to return an instance of the object itself. This sort of fancy-looking way of instantiating classes is actually very purposeful and represent a variant of the Factory Method Design Pattern that has several interesting benefits.
# Setup the physical world sceneDesc = PhysxSceneDesc() sceneDesc.setGravity(Vec3(0, 0, -9.81)) self.scene = self.physx.createScene(sceneDesc) # Material of the world m0 = self.scene.getMaterial(0) m0.setRestitution(0.1) m0.setStaticFriction(0.3) m0.setDynamicFriction(0.3)
For each object you want to be part of the physics simulation, you have to create a PhysX Actor and set it up with a PhysX Shape (for collisions) and a PhysX Body (for rigid body dynamics). You can set both shape and body in the actor descriptor and then create the actor into the physics scene. Here is how a box actor is created (note that you still need your original Panda model of the box, that's the visible version of the box):
# Collision shape for the box shapeDesc = PhysxBoxShapeDesc() shapeDesc.setDimensions(Vec3(0.3, 0.3, 0.3)) # Rigid body for the box bodyDesc = PhysxBodyDesc() bodyDesc.setMass(10.0) # PhysX actor for the box actorDesc = PhysxActorDesc() actorDesc.setBody(bodyDesc) actorDesc.setName('Box') actorDesc.addShape(shapeDesc) actorDesc.setGlobalPos(Point3(1, 0, 2)) self.boxActor = self.scene.createActor(actorDesc)
To see the effect of gravity on the box, you will now have to start a task that copies the position of the physical box to the position of the visual box at every frame, and advances the physics simulation world by one step:
#--- PhysX ---- taskMgr.add(self.simulate, 'PhysX Simulation') def simulate(self, task): dt = globalClock.getDt() self.scene.simulate(dt) # Split the computation from... self.scene.fetchResults() # ...results fetching so to support multi-threading # Set the position and rotation of the graphical box from the physical box self.box.setPosQuat(self.boxActor.getGlobalPos(), self.boxActor.getGlobalQuat()) return task.cont
Test this and make sure gravity is working as expected. Make sure to comment out the LerpPosInterval for the box since you no longer need it! You should also change the left mouse button handler so that it sets the position of both the visual box and the physical box back to the original position (to maintain similar functionality as before). You will also want to set the linear velocity of the box to 0 like this self.boxActor.setLinearVelocity(Vec3(0.0, 0.0, 0.0)), otherwise the box will keep going faster and faster.
def simulate(self, task): dt = globalClock.getDt() self.scene.simulate(dt) self.scene.fetchResults() self.box.setPosQuat(self.boxActor.getGlobalPos(), self.boxActor.getGlobalQuat()) self.door.setPosQuat(self.doorActor.getGlobalPos(), self.doorActor.getGlobalQuat()) return task.cont
You may notice that the door naturally falls down along with the box. Bug? No, just gravity! There is nothing holding up the door, so it just falls down to infinity. We'll fix that next.
self.trailActor.setBodyFlag(PhysxEnums.BFKinematic, 1)
PhysX provides a number of joints that constrain actors together in different ways. We want the door to slide through the trail, so we are going to use a point-on-line joint. This kind of joint will constrain the origin of the door to move only along a line of the trail actor, enough to achieve a sliding effect. But we have a problem here! Having the door constrained in only one point means that, when a box will fall on the door from above, the door might start spinning around its origin pushed by the mass of the falling box. To avoid this we need to freeze the door rotations along the three axis, kind of having the door installed into an opening of the floor. You create a point-on-line joint with all the necessary setting and attach door and trail together like this:
# Joint jointDesc = PhysxPointOnLineJointDesc() jointDesc.setName('Point-On-Line Joint') jointDesc.setActor(0, self.trailActor) # Attach trail... jointDesc.setActor(1, self.doorActor) # ... and door together jointDesc.setGlobalAnchor(self.doorActor.getGlobalPos()) # Attach them on this point jointDesc.setGlobalAxis(Vec3(1, 0, 0)) # Slides down x-axis # Avoid spinning by freezing the door rotation axis self.doorActor.setBodyFlag(PhysxEnums.BFFrozenRotX, 1) self.doorActor.setBodyFlag(PhysxEnums.BFFrozenRotY, 1) self.doorActor.setBodyFlag(PhysxEnums.BFFrozenRotZ, 1) # Create the joint joint = self.scene.createJoint(jointDesc) # Joint limits set by plane joint.addLimitPlane(Vec3(-1, 0, 0), Point3(1, 0, 0)) joint.addLimitPlane(Vec3( 1, 0, 0), Point3(-1, 0, 0))
The last two lines here set the limits for the sliding door. While along the joint axis, the door will be not allowed to go behind the two limit planes. Now try replacing the LerpPosInterval calls, for opening and closing the door, with something more physics-oriented. Call self.doorActor.setLinearVelocity(Vec3(-0.5, 0.0, 0.0)) to slide the door to the left.