Table of Contents

Lab 2 - Making Scenes in Panda 3D

In-Class Excercises

Before You Start

Making a Virtual Room in Panda 3D

  1. Loading and Posing an Object:
    Create a new python script called myscene.py that does the following:
    1. Moves the camera to the location (-0.5, -3.5, 0.5), changes its heading by -45 degrees.
    2. Loads and displays the model “Models/television.egg”, placing it at location (2,-2,0). Scale the model down to 15% and rotate it such that you can see the front of the television screen.
      # HINT - A basic program showing a scene with one object from a fixed camera 
      # NOTE - Numeric values are arbitrary in this example (you need to change them for your program)
      import direct.directbase.DirectStart
      # Disabling user controlled camera
      base.disableMouse()
      # Placing the camera in world space
      base.camera.setPos(<x>,<y>,<z>) # Location in world coordinates
      base.camera.setHpr(<heading>,<pitch>,<roll>) # Orientation in degrees 
      # Loading a new ready-made model
      model = loader.loadModel(<model filename>)  # 3D model to load from disk, e.g. "MyModel.egg"
      model.setScale(<scale factor>)  # Uniform scaling of the model, e.g. 0.7
      model.setPos(<x>,<y>,<z>)  # Location in world coordinates
      model.setHpr(<heading>,<pitch>,<roll>) # Orientation in degrees 
      # Adding the model to the Scene Graph under the root called 'render'
      model.reparentTo(render)
      # Start main loop
      run()
  2. Finding a Node and Applying Texture:
    Add a moving image to the television screen by first finding the NodePath object representing the 'screen' sub-tree of the television model and then giving it (and only it) the texture “Models/Textures/shuttle.avi”. See also the section on Searching the Scene Graph in the Manual.
    # HINT - Finding a nodepath to a named sub-node
    nodepath = model.find("**/<name>")    
    # HINT - Loading a new texture
    mytexture = loader.loadTexture(<filename>)  # E.g. "MyTexture.png"
    # HINT - Setting a texture property
    model.setTexture(mytexture, 1) # Places the textur on a model 
  3. Creating a World Class and Simple Navigation:
    Now you will place your scene inside a new Python class. One reason to do this is that your code can be more modular, but another reason is that by making your class inherit methods and attributes from DirectObject, you get a lot of cool functionality - including event handling!
    1. Create a new class called World, derived from DirectObject, and move the creation of the television (but not your camera) into its constructor and then instantiate this world before calling run() in your main program. Test this before continuing.
    2. The example below shows how the constructor can set up event handlers for specific key-strokes (in this case the key 's'). You can check the manual pages for EventHandlers to understand how this works. This code makes you move the camera backwards as you hold down the 's' key. Add code for moving the camera forward with the 'w' key and for rotating the camera left and right with the 'a' and 'd' keys. You should be able to do this by accepting more events, storing more keys and adding more kinds of updates inside update_camera.
      from direct.showbase.DirectObject import DirectObject  
      class World(DirectObject):
         """ An interactive scene """
       
          def __init__(self):
              """ The constructor, which builds the scene and sets up user control """
       
              # Code for building the scene could go here...
       
              # Set up movement keys
              self.keystate = {'back':0} # Just a member variable to store the state of each key
              self.accept('s', self.set_keystate, ['back',1])  # Handler for 's' press
              self.accept('s-up', self.set_keystate, ['back',0]) # Handler for 's' release
              self.lasttime = 0
              taskMgr.add(self.update_camera, "camerathread") # Start a new thread which then will loop
                                                              # forever to update the camera position
       
          def set_keystate(self, key, state):
              """ Set a stored state (either 0 or 1) for a specified key ('back',...)"""
              self.keystate[key] = state # Updates the member variable holding the states of the keys
       
          def update_camera(self, task):
              """ 
              Update camera position and/or rotation for all active key states.
              """
              elapsed = task.time - self.lasttime
              if self.keystate['back'] is 1:   # Checks the member variable for the current state of a given key
                  base.camera.setPos(camera,0,-0.7*elapsed,0)  # "setPos" with 4 parameters makes the translation 
                                                               # relative to the first parameter
              self.lasttime = task.time
              return task.cont
      # HINT - This might help you use a simple loop to set up the keys without having to repeat the
      #        'accept' command 8 times!
      key_map = (('s',['back',1]),('s-up',['back',0]),
                 ('w',['forward',1]),('w-up',['forward',0]),
                 ('a',['left',1]),('a-up',['left',0]),
                 ('d',['right',1]),('d-up',['right',0]))
  4. Making a Flexible Room Generator:
    Add a new class called Room (see below) which you'll use to generate a setting like the one shown in the screenshot above. The Room's constructor expects three data lists that define the shape of the room. The data lists describe the placement of 1×1 panels for walls, floor and ceiling. All list items are tuples that contain the panel x and y location, but the third value in the tuple (if there is one) depends on the type of panel being created (see below). To create a tiny room with a light in the ceiling, you would instantiate the Room class like this:
    # Building a very small room using the Room class
    room_pathnode = render.attachNewNode("Room") # Create an empty parent node for the Room
    room = Room(room_pathnode,
                [(0,0,0),(1,0,-90),(1,-1,-180),(0,-1,-270)],  # Walls
                [(0,-1)],                                     # Floor
                [(0,0,True)])                                 # Ceiling with light

    Here is the Room class, only with the code to construct walls included:

    class Room:    
        def __init__(self, parent_pathnode, wall_data, floor_data, ceiling_data):
            """
            Generate a room with walls, floor and a ceiling from lists of 1x1 panel
            locations and orientations.
                'parent_pathnode' = Pathnode immediately above this one in the scene graph
                'wall_data' = List of wall panel touples: (x loc, y loc, angle)
                'floor_data' = List of floor panel touples: (x loc, y loc)
                'ceiling_data' = List of ceiling panel touples: (x loc, y loc, has light?)
            """
            self.parent = parent_pathnode
            self.wall_data = wall_data
            self.floor_data = floor_data
            self.ceiling_data = ceiling_data
            self.panel = loader.loadModel("Model/grid.egg") # This is just a rectangular polygon mesh
            self.create_floor()
            self.create_ceiling()
            self.create_walls()
            self.create_lights()
     
        def create_walls(self):
            """ Create the walls according to Room data, with panel textures """
            wall_clear    = loader.loadTexture("Models/Textures/wall_tanned_clear.png")     
            for wallpanel_data in self.wall_data:
                wallpanel = self.parent.attachNewNode("wall panel") # Create a new node
                self.panel.instanceTo(wallpanel)         # Copy the geometry into place
                wallpanel.setPos(wallpanel_data[0],wallpanel_data[1],0)
                wallpanel.setHpr(wallpanel_data[2],0,0)
                wallpanel.setTexture(wall_clear)
     
        def create_floor(self):
            """ Create the floor according to Room data """
            # Fill this in
     
        def create_ceiling(self):
            """ Create the ceiling, with ceiling light textures, according to Room data """ 
            # Fill this in
     
        def create_lights(self):
            """ Create both the ambient light and spotlights for every ceiling light """
            # Leave this for a later problem.

    Fill in the code for create_floor and create_ceiling (see asset names below) and test with the data set for the tiny room above. You should instantiate the Room from within the constructor of the World class.

    # USEFUL ASSETS:
    # "Models/Textures/wall_tanned_clear.png" 
    # "Models/Textures/floor_concrete.png"
    # "Models/Textures/ceiling_tanned.png"
    # "Models/Textures/ceiling_tanned_light.png"
  5. Making Randomized Panels and a Larger Room:
    Modify the create_wall method to pick a random texture for each panel for a greater variety (use the various wall panel assets below). Construct a larger room now, that resembles the room in the screenshot above, by passing the right data into the Room's constructor. The following blueprint will give you all the information you need:

    # MORE USEFUL ASSETS:
    # "Models/Textures/wall_tanned_clear.png" 
    # "Models/Textures/wall_tanned_door.png"
    # "Models/Textures/wall_tanned_window.png"
    # "Models/Textures/wall_tanned_vent.png" 
  6. Adding Lights:
    Fill in the code for the create_lights method. Create one ambient light with the color (0.4,0.4,0.4,1). Create one spotlight with the color (0.6,0.6,1.0,1) and attenuation (0,0,0), facing down from the ceiling (starting at height 1.25).
    # HINT - Creating lights
    # Set up the global lighting for general illumination
    ambient_source = AmbientLight('ambient')
    ambient_source.setColor(Vec4(1,1,1,1))
    ambientnp = root.attachNewNode(ambient_source)
    root.setLight(ambientnp)
    # Set up a spotlight for localized illumination
    lens = PerspectiveLens()
    lens.setNearFar(0.1, 2.0)
    spot_source = Spotlight('spotlight')
    spot_source.setColor(Vec4(1,1,1,1))
    ## spot_source.showFrustum() # Uncomment for great debug information
    spot_source.setLens(lens)
    spotnp = root.attachNewNode(spot_source)
    spotnp.setPos(Vec3(1,1,1))
    spotnp.setHpr(0,0,0)
    root.setLight(spotnp)
  7. Attaching Lights to Fixtures:
    Modify the spotlight creation in the create_lights methods so that it creates a spotlight for each of the ceiling panels that have light fixtures in them.