====== Lab 2 - Making Scenes in Panda 3D ======
===== In-Class Excercises =====
==== Before You Start ====
* Download and unzip the [[http://www.ru.is/kennarar/hannes/classes/ve2010/Lab2Assets.zip|Lab 2 Asset File]] into your working directory (where you will be writing and running your python script).
==== Making a Virtual Room in Panda 3D ====
{{public:t-vien-07-1:ve-lab2-screenshot.jpg|}}
- **Loading and Posing an Object:**\\ Create a new python script called ''myscene.py'' that does the following:
- Moves the **camera** to the location (-0.5, -3.5, 0.5), changes its **heading** by -45 degrees.
- 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(,,) # Location in world coordinates
base.camera.setHpr(,,) # Orientation in degrees
# Loading a new ready-made model
model = loader.loadModel() # 3D model to load from disk, e.g. "MyModel.egg"
model.setScale() # Uniform scaling of the model, e.g. 0.7
model.setPos(,,) # Location in world coordinates
model.setHpr(,,) # Orientation in degrees
# Adding the model to the Scene Graph under the root called 'render'
model.reparentTo(render)
# Start main loop
run()
- **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 [[http://www.panda3d.org/wiki/index.php/Searching_the_Scene_Graph|Searching the Scene Graph in the Manual]].
# HINT - Finding a nodepath to a named sub-node
nodepath = model.find("**/")
# HINT - Loading a new texture
mytexture = loader.loadTexture() # E.g. "MyTexture.png"
# HINT - Setting a texture property
model.setTexture(mytexture, 1) # Places the textur on a model
- **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!
- 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.
- 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 [[http://www.panda3d.org/wiki/index.php/Event_Handlers|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]))
- **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 1x1 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"
- **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:\\ {{public:t-vien-07-1:scene_gridview.jpg|}} \\
# 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"
- **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)
- **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.