import direct.directbase.DirectStart from direct.showbase.DirectObject import DirectObject from pandac.PandaModules import * from direct.interval.IntervalGlobal import *
In your code, you should disable the mouse and then place the camera at (1,-8,5) with a pitch of -25 degrees. Then define a class called World that derives from DirectObject (this allows World to receive events). In the constructor of the World class load and display two models: ./Models/factoryfloor.egg and ./Models/box.egg. Position the box at (1,0,2), scale it down to 30% and give it a heading of 45 degrees. Make sure you can see both the floor and the box.
<pathnode>.find
, and call it door. Create two LerpPosIntervals for the door, one to open it by moving it to location (-1,0,0) and one to close it by moving it to location (1,0,0). You can play with the duration but 2.0 seconds is a good place to start. Make the World accept a 'space' (spacebar key) event and call the associated event handler self.door_handler. In that handler, either start the opening interval for the door or the closing interval, depending on whether it's open or closed already (you'll need a new boolean member variable to keep track of that). Make sure you can now open and close the door with the spacebar.# HINT - Creating and attaching a CollisionSphere nodepath = object.attachNewNode(CollisionNode(<name>)) nodepath.node().addSolid(CollisionSphere(<x>,<y>,<z>,<radius>)) # You typically keep it at (0,0,0)
Similarly, attach a new CollisionNode to the door pathnode and call it door. This time however, add a CollisionPolygon to the node of the new pathnode. The verticies of this polygon are (-1,-1,0), (1,-1,0), (1,1,0) and (-1,1,0) in that order.
# HINT - The CollisionPolygon nodepath.node().addSolid(CollisionPolygon(Point3(x1,y1,z1),Point3(x2,y2,z2),Point3(x3,y3,z3),Point3(x4,y4,z4)))
You can call show() on your new collision node nodepaths to see your collision solids as semi-transparent objects when you view your scene. Verify that your solids are in the right place and then remove the calls to show().
collhandler = CollisionHandlerEvent() collhandler.addInPattern('%fn-into-%in') # The event pattern
Then initialize the global collision traverser as follows:
base.cTrav = CollisionTraverser('world traverser')
and then add the box collision nodepath as a new collider and associate it with the collision handler you just created.
#HINT - Adding a new collider to a traverser base.cTrav.addCollider(<nodepath>, <handler>)
Make the world accept an event called “box-into-door” and call a method named self.collision where you can put things that should happen when a collision with this event name occurs. Notice that this method receives a parameter called collEntry which contains information about the collision event. The following is a simple collision event receiver:
def collision(self, collEntry): print collEntry
In addition to printing out some information like is done in this example, you should call the pause() method on the LerpPosInterval associated with the box object. This will stop the progress of the box movement. Verify that the box stops now when the door is closed but goes all the way down when the door is open.
thudsound = loader.loadSfx("thud.wav") closesound = loader.loadSfx("close.wav")
It should be pretty clear where to put the thudsound, but the closesound needs to be wrapped into a SoundInterval object and then sequenced with the closing door LerpPosInterval so that it happens at the end of the movement.
# HINT - Sequencing intervals newinterval = Sequence(<interval1>, <interval2>, ... )
import ode
. Follow these steps to redo the factory scene with ODE, simply by modifying the code you have so far. (This code is based on the excellent PyODE tutorials as well as on several Panda 3D forum posts) world
) and another for collision handling (called space
). So the first thing you do (you can do all of this in the constructor of your World class) is to instantiate and set up these two environments: self.world = ode.World() # Create a physics/dynamics environment self.world.setGravity((0,0,-9.81)) # Add earth gravity in the negative z-dimension self.space = ode.Space() # Create a collision environment
For each object you want to be part of the physics simulation, you have to create a physical body. Here is how the physical body of the box is created (note that you still need your original Panda model of the box, that's the visible version of the box):
self.boxbody = ode.Body(self.world) # Instantiate a physical body in the physical environment self.boxbody.size = (0.3, 0.3, 0.3) # Store the size of the body self.boxbody.setPosition((1,0,2)) # Place the body in the same location as in the Panda environment self.boxbody.addForce((0,0,1000)) # This is just for fun: give the box an upward kick at the beginning self.boxmass = ode.Mass() # Define a physical mass for the box self.boxmass.setBox(500, *self.boxbody.size) # The mass is defined as density and size self.boxbody.setMass(self.boxmass) # Add the mass to the physical body of the box
Now that you have a physical representation of the box, you still need to make an ODE collision solid for it and put into the collision environment. This is how:
self.boxsolid = ode.GeomBox(self.space, self.boxbody.size) self.boxsolid.setBody(self.boxbody)
To see the effect of gravity (and the kicking force) 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:
taskMgr.add(self.simulate, 'ODE Simulation') def simulate(self, task): x,y,z = self.boxbody.getPosition() # Get position of the simulated physical box self.box.setPos(Vec3(x,y,z)) # Set the position of the visual box self.world.step(0.04) # Increment time in the physical simulation return Task.cont # Keep this task running forever
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.boxbody.setLinearVel( (0,0,0) )
, otherwise the box will keep going faster and faster
def set_model_odeposrot(model, pos, rot): gquat = Quat () gquat.setFromMatrix (Mat3 (*rot)) gpos = VBase3 (pos [0], pos [1], pos [2]) model.setPosQuat (gpos, gquat)
Your simulate
task should now look like this:
def simulate(self, task): set_model_odeposrot(self.box, self.boxbody.getPosition(), self.boxbody.getRotation()) set_model_odeposrot(self.door, self.doorbody.getPosition(), self.doorbody.getRotation()) self.world.step(0.04) return Task.cont
You may notice that the door naturally falls down along with the box since there is nothing holding it up! We'll fix that next.
self.doorslider = ode.SliderJoint(self.world) # Instantiating a new slider joint self.doorslider.attach(self.doorbody, ode.environment) # Using the joint to attach door body to environment self.doorslider.setAxis((1,0,0)) # Slides along the x-axis self.doorslider.setParam(ode.paramFMax, 100) # Sets the force of a motor attached to this joint
The last line here actually attaches a little motor to the joint, which we can give a certain velocity whenever we want to move the joint autonomously. Try replacing the LerpPosInterval calls for opening and closing the door, with calls like this: self.doorslider.setParam(ode.paramVel, 0.3)
, where 0.3 is the velocity (can also be negative to go in the other direction).
World
constructor, create the following member variable:self.contactgroup = ode.JointGroup() # Holds a set of joints
Inside the simulate
task, you now have to ask the collision environment to check for near collisions like this:
self.space.collide((self.world, self.contactgroup), self.near_callback)
Where you then have a new member function called near_callback
that looks like this:
def near_callback(self, args, solid1, solid2): contacts = ode.collide(solid1, solid2) # Returns the actual collisions between two solids world, contactgroup = args for c in contacts: c.setBounce(0.2) # How much bounce should happen from this collision c.setMu(5000) # How much friction there should be between the solids j = ode.ContactJoint(world, contactgroup, c) # A temporary joint joins the solids together j.attach(solid1.getBody(), solid2.getBody())
The very last thing to do is to call self.contactgroup.empty()
right after you have called self.world.step(0.04)
in order to start looking for fresh collisions after each iteration. Test to see if everything is working.