====== Factory Scene with PhysX (unfairly only for Windows) ======
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)\\
- For this to work, you need to first install [[http://www.ru.is/kennarar/hannes/share/PhysX_9.10.0513_SystemSoftware.exe|The PhysX Software System]]
- In your code, you need to import the Panda PhysX classes.
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
\\
- The PhysxManager is your entry point to the PhysX engine. It is a singleton, that is a class that can have only a single global instance.
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 [[http://en.wikipedia.org/wiki/Factory_method_pattern|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.\\
- To introduce collision with the door, you will need to create an actor for the door with both collision shape and rigid body, just like you did for the box. Try using dimensions of **(1.0, 1.0, 0.1)** and mass of **500.0**. Don't forget to update the door's visual position based on the physical position in the simulation task! Your simulate task should now look like this:
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.\\
- You need to attach the door to its door trail so that it doesn't fall. The door trail will be a new PhysX actor and you can attach the door to it using a so called actor joint. We need to set the trail actor as kinematic and that means not affected by forces (such as gravity). You can create a trail actor in the same way you did for the door. Set dimensions **(1.0, 1.0, 0)** and mass **10.0**. You also may want to move the trail from the door position by a certain offset. Try with **Vec3(-2.0, 0.0, 0.0)**. Once you have created your trail actor, you can make it kinematic like this: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.\\
- Finally, make sure to remove all previous collision handling, since PhysX will take care of it from now on. Unfortunately, the events generated by the physics engine behave a bit differently than the events created by Panda's collision system, so playing a sound when collision occurs gets tricky. Don't worry about that for now, and enjoy the physical simulation without sounds.\\
- For extra awesomeness try the following: Create an instance of an AcmeBox every time you press the mouse button. Maintain a list of created boxes in your factory and iterate over that list in the simulation function to update their positions and rotations. This way you can fill the scene with tumbling boxes and be happy to mess around!