Table of Contents

Lab 4 - Events, Collision and Movement

Before You Start

Making a functional trap door

  1. Creating a New Scene:
    Create a new program called “factory.py” and include the following imports at the beginning of the file:
    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.

  2. Opening and Closing the Door:
    The floor model contains a door sub-object. Acquire a nodepath to it, using <nodepath>.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.
  3. Using blendType and name to Improve Door Movement:
    When you create the opening and closing intervals, give the constructor attribute blendType a value of “easeInOut” for a bit more realistic trap door motion (you can play with the other values “easeIn” and “easeOut” as well). Also, give the attribute name the same name for both intervals - this ensures that the first interval gets cancelled when the second one starts.
  4. Creating Collision Solids:
    Attach a new CollisionNode to the box nodepath and give it the name “box” (you pass the name into the constructor of the CollisionNode). To the node of this new nodepath, add a new collision solid of the type sphere and give it a radius of 1.5.
    # 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 nodepath and call it door. This time however, add a CollisionPolygon to the node of the new nodepath. 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().

  5. Making the Box Move:
    Create a new LerpPosInterval for the box and have it move to the location (1,0,-1). Make the World accept a 'mouse1' event and call a method that starts this interval playing. A duration of 3.0 seconds for this movement seems good and you might want to try different blend types. Verify that you can make the box move by clicking the left mouse button. Notice that the box goes right through the door.
  6. Making a Collision Happen:
    In the World constructor, create a new instance of a CollisionHandlerEvent and add the event name pattern shown below:
    collhandler = CollisionHandlerEvent()
    collhandler.addInPattern('%fn-into-%in') # The event name pattern: "<from name>-into-<into name>"

    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.

  7. Adding Sounds:
    To add sounds to this scene, you can use thud.wav for when the box hits the door and close.wav for when the door slams shut. You don't have to localize these sounds, just load them in like this:
    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>, ... )
  8. A Physics Based Version:
    In the version of the factory above, you were faking movement in a physical world by using interpolators to move objects around. You had to do everything by hand, and even so, not everything is looking right. For example, if the box hits the edge of the door, the box stops, even if physics tell us that the box should just tip over and fall off the edge. In fact, if it lands on the door, it doesn't even move with the door. All of these details can be taken care of with a good simulation of physics and rigid body dynamics. One such engine is the Open Dynamics Engine (ODE) and another such engine is NVidia PhysX. Both of these engines are supported by Panda3D. ODE is an open source project, and therefore the whole source (which is in C++) is available for those that are interested. PhysX is a commercial product, and is somewhat more robust and precise (used in a number of high-end games), but you only get access to run-time binaries in addition to the API. In this part of the lab, you can choose to create the above scene with ODE or PhysX (or both if you wish). Simply follow the instructions for each below: