PyOgre Beginner Tutorial 1        

PyOgre Beginner Tutorial 1: The SceneNode, Entity, and SceneManager constructs

Original version by Clay Culver

Note: This was written for PyOgre 1.0.5, and is known to work with it.

This is not for the current version of PyOgre

Please visit the PyOgre Wiki for an updated tutorial.

Prerequisites

This tutorial assumes you have knowledge of Python programming and you have already installed PyOgre. No knowledge of Ogre is assumed for this tutorial.

Introduction

In this tutorial I will be introducing you to the most basic Ogre constructs: SceneManager, SceneNode, and Entity objects. We will not cover a large amount of code; instead I will be focusing on the general concepts for you to begin learning Ogre.

As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it. There is no substitute for actual programming to get familiar with these concepts! Resist the urge to simply read along. If you are having problems you can download the source here.

Note that this program needs you to use the SampleFramework.py file. You can find this in the demos/ directory, or if you are having difficulty finding it, you may download it here. You will also need to ensure that you have the resources.cfg, and media.cfg files in the same directory as your source code, and that the resources.cfg file points to the correct path for your Media directory. Generally if you want a "quick start" for this, the best thing to do is to create your source file in the same directory as the pyogre demos.

Getting Started

Base Code

We will be using a pre-constructed code base for this tutorial. You should ignore all of the code except for what we will be adding to the _createScene method. In a later tutorial we will go in depth explaining how Ogre applications work, but for now we will be starting at the most basic level. Create a python source file called "basic_1.py" and add the following code:

from pyogre import ogre
 import SampleFramework
 
 class TutorialApplication(SampleFramework.Application):
     def _createScene(self):
         pass
 
 if __name__ == '__main__':
     ta = TutorialApplication()
     ta.go()

Once the program is working, use the WASD keys to move, and the mouse to look around. The Escape key exits the program. However, since we have not added any objects to the scene, there will not be anything to look at.

from pyogre import ...

In the above code, we imported the ogre module from the pyogre package. It is very important to note that we did not import everything from the module. It is safe to use this code:

from pyogre import *

However, unless you are actually using everything in PyOgre I recommend not doing that. PyOgre is a very large module, and each of its components takes time (and memory) to load. This is usually not a problem for most python libraries, but it's hard to stress exactly how large PyOgre is. The wrapper for PyOgre spans over 200,000 lines of C++ code, driven by over 17,000 lines of Python code. PyCEGUI wrapper spans over 80,000 lines of C++ code, driven by over 4,000 lines of Python code. Of course, Python handles loading and using modules very efficiently and you should not really notice much of an impact for this. Even still, if you are not using CEGUI, there is no reason to include it in the import statement.

How Ogre Works

A broad topic. We will start with SceneManagers and work our way to MovableObjects and SceneNodes. These three classes are the fundamental building blocks of Ogre applications.

SceneManager Basics

Everything that appears on the screen is managed by the SceneManager (fancy that). When you place objects in the scene, the SceneManager is the class which keeps track of their locations. When you create Cameras to view the scene (which we will cover in a later tutorial) the SceneManager keeps track of them. When you create planes, billboards, lights...and so on, the SceneManager keeps track of them.

There are multiple types of SceneManagers. There are SceneManagers that render terrain, there is a SceneManager for rendering BSP maps, and so on. You can see the various types of SceneManagers listed here. We will cover more about other SceneManagers as we progress through the tutorials.

MovableObject Basics

A MovableObject is anything that can be placed into a scene and moved around.

An Entity is one of the types of object that you can render on a scene (and it is a subclass of MovableObject). You can think of an Entity as being anything that's represented by a 3D mesh. A robot would be an entity, a fish would be an entity, the terrain your characters walk on would be a very large entity. Things such as Lights, Billboards, Particles, Cameras, etc would not be an Entity, but they are MovableObjects.

One thing to note about Ogre is that it separates renderable objects from their location and orientation. This means that you cannot directly place an Entity in a scene. Instead you must attach the Entity to a SceneNode object, and this SceneNode contains the information about location and orientation.

SceneNode Basics

As already mentioned, SceneNodes keep track of location and orientation for all of the objects attached to it. When you create an Entity, it is not ever rendered on the scene until you attach it to a SceneNode. Similarly, a SceneNode is not an object that is displayed on the screen. Only when you create a SceneNode and attach an Entity (or other object) to it is something actually displayed on the screen.

SceneNodes can have any number of objects attached to them. Let's say you have a character walking around on the screen and you want to have him generate a light around him. The way you do this would be to first create a SceneNode, then create an Entity for the character and attach it to the SceneNode. Then you would create a Light object and attach it to the SceneNode. SceneNodes may also be attached to other SceneNodes which allows you to create entire hierarchies of nodes. We will cover more advanced uses of SceneNode attachment in a later tutorial.

One major concept to note about SceneNodes is that a SceneNode's position is always relative to its parent SceneNode, and each SceneManager contains a root node which all other SceneNodes are attached.

Your first Ogre application

Now go back to the code we created earlier. Find the TutorialApplication._createScene member function. We will only be manipulating the contents of this function in this tutorial. The first thing we want to do is set the ambient light for the scene so that we can see what we are doing. We do this by calling the setAmbientLight function and specifying what color we want. Note that the ColourValue constructor expects values for red, green, and blue in the range between 0 and 1. Add this line to createScene:

sceneManager = self.sceneManager
        sceneManager.ambientLight = ogre.ColourValue(1, 1, 1)

The next thing we need to do is create an Entity. We do this by calling the SceneManager's createEntity member function:

ent1 = sceneManager.createEntity("Robot", "robot.mesh")

Ok several questions should pop up. First of all, where did self.sceneManager come from and what are we calling createEntity with? The self.sceneManager variable contains the current SceneManager object (this is done for us by the SampleFramework.Application class). The first parameter to createEntity is the name of the Entity we are creating. All entities must have a unique name. You will get an error if you try to create two entities with the same name. The "robot.mesh" parameter specifies the mesh we want to use for the Entity. Again, the mesh that we are using has been preloaded for us by the SampleFramework.Application class.

Now that we have created the Entity, we need to create a SceneNode to attach it to. Since every SceneManager has a root SceneNode, we will be creating a child of that node:

node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")

This statement calls the createChildSceneNode method of the root SceneNode. The parameter to createChildSceneNode is the name of the SceneNode we are creating. Like the Entity class, no two SceneNodes can have the same name.

Finally, we need to attach the Entity to the SceneNode so that the Robot has a location to be rendered at:

node1.attachObject(ent1)

And that's it! Run your application. You should see a robot standing on the screen.

Coordinates and Vectors

Before we go any further, we need to talk about screen coordinates and Ogre Vector objects. Ogre (like many graphics engines) uses the x and z axis as the horizontal plane, and the y axis as your vertical axis. As you are looking at your monitor now, the x axis would run from the left side to the right side of your monitor, with the right side being the positive x direction. The y axis would run from the bottom of your monitor to the top of your monitor, with the top being the positive y direction. The z axis would run into and out of your screen, with out of the screen being the positive z direction.

Notice how our Robot is facing along the positive x direction? This is a property of the mesh itself, and how it was designed. Ogre makes no assumptions about how you orient your models. Each mesh that you load may have a different "starting direction" which it is facing.

Ogre uses the Vector class to represent both position and direction (there is no Point class). There are vectors defined for 2 (Vector2), 3 (Vector3), and 4 (Vector4) dimensions, with Vector3 being the most commonly used. If you are not familiar with Vectors, I suggest you brush up on it before doing anything serious with Ogre. The math behind Vectors will become very useful when you start working on complex programs.

Adding another Object

Now that you understand how the coordinate systems work, we can go back to our code. In the five lines that we have written, nowhere did we specify the exact location that we want our Robot to appear at. A large majority of the functions in Ogre have default parameters for them. For example, the SceneNode.createChildSceneNode member function in Ogre has three parameters: the name of the SceneNode, the position of the SceneNode, and the initial rotation (orientation) the SceneNode is facing. The position, as you can see, has been set for us to the coordinates (0, 0, 0). Lets create another SceneNode, but this time we'll specify the starting location to be something other than the origin:

ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
        node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))
        node2.attachObject(ent2)

This should look familiar. We have done the exact same thing as before, with two exceptions. First of all, we have named the Entity and SceneNode to something slightly different. The second thing we have done is specified that the starting position will be 50 units in the x direction away from the root SceneNode (remember that all SceneNode positions are relative to their parents). Compile and run the demo. Now there are two robots side-by-side.

Vectors and ColourValue

As you can imagine, certain classes are used over and over for a lot of ogre. Vector3 and ColourValue are two of those. In order to make your life easier, we have made it so that any time you need to use a Vector or a ColourValue, you can use a tuple instead. That means instead of typing ogre.Vector3(0, 50, 0), you can simply type (0, 0, 0). Instead of ogre.ColourValue(1, 1, 1) you can type (1, 1, 1) now. Make this change in the _createScene function to use this feature. Your code should now look like this:

sceneManager = self.sceneManager
  
        sceneManager.ambientLight = (1, 1, 1)
  
        ent1 = sceneManager.createEntity("Robot", "robot.mesh")
        node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
        node1.attachObject(ent1)
  
        ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
        node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", (50, 0, 0))
        node2.attachObject(ent2)

Entities more in Depth



The Entity class is very extensive, and I will not be covering how to use every portion of the object here...just enough to get you started. There are a few immediately useful member functions in Entity that I'd like to point out.

The first is the "visible" attribute. You can set any Entity to be visible or not by simply setting this attribute to True or False. If you need to hide an Entity, but later display it, then call this function instead of destroying the Entity and later recreating it. Note that you don't need to "pool" Entities up. Only one copy of any object's mesh and texture are ever loaded into memory, so you are not saving much by trying to pool them. The only thing you really save is the creation and destruction costs for the Entity object itself, which is relatively low.

The "name" attribute returns the name of an Entity (note that it is read-only, you cannot change the name of an Entity after creating it). The "parentSceneNode" attribute returns the SceneNode the entity is attached to.

SceneNodes more in Depth

The SceneNode class is very complex. There are a lot of things that can be done with a SceneNode, so we'll only cover some of the most useful.

You can get and set the position of a SceneNode by using the "position" attribute (always relative to the parent SceneNode). You can move the object relative to its current position by using the translate method.

SceneNodes not only set position, but they also manage scale and rotation of the object as well. You can set the scale of an object with scale attribute, and actually scale the object using the scaleBy function (see the Scale section later for more information). You can use the yaw, roll, and pitch functions to rotate objects. You can use resetOrientation to reset all rotations done to the object. You can also use the "orientation" attribute to get and set the orientation, and the rotate function for more advanced rotations. We will not be covering Quaternions until a much later tutorial though.

You have already seen the attachObject function. These related functions and attributes are also useful if you are looking to manipulate the objects that are attached to a SceneNode: the "numAttachedObjects" attribute, getAttachedObject function (there are multiple versions of this function), detachObject function (also multiple versions), detachAllObjects function. There are also a whole set of functions for dealing with parent and child SceneNodes as well.

Since all positions/translating is done relative to the parent SceneNode, we can make two SceneNodes move together very easily. We currently have this code in application:

ent1 = sceneManager.createEntity("Robot", "robot.mesh")
        node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
        node1.attachObject(ent1)
 
        ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
        node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))
        node2.attachObject(ent2)

If we change the 6th line from this:

node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))

To this:

node2 = node1.createChildSceneNode("RobotNode2", (50, 0, 0))

Then we have made RobotNode2 a child of RobotNode. Moving node1 will move node2 along with it, but moving node2 will not affect node1. For example this code would move only RobotNode2:

node2.translate(( 10, 0, 10 ));

The following code would move RobotNode, and since RobotNode2 is a child of RobotNode, RobotNode2 would be moved as well:

node1.translate((25, 0, 0));

If you are having trouble with this, the easiest thing to do is to start at the root SceneNode and go downwards. Lets say (as in this case), we started node1 and (0, 0, 0) and translated it by (25, 0, 0), thus node1's position is (25, 0, 0) relative to its parent. node2 started at (50, 0, 0) and we translated it by (10, 0, 10), so its new position is (60, 0, 10) relative to it's parent.

Now lets figure out where these things really are. Start at the root SceneNode. It's position is always (0, 0, 0). Now, node1's position is (root + node1): (0, 0, 0) + (25, 0, 0) = (25, 0, 0). Not surprising. Now, node2 is a child of node1, so its position is (root + node1 + node2): (0, 0, 0) + (25, 0, 0) + (60, 0, 10) = (85, 0, 10). This is just an example to explain how to think about SceneNode position inheritance. You will rarely ever need to calculate the absolute position of your nodes.

Lastly, note that you can get both SceneNodes and Entities by their name by calling getSceneNode and getEntity methods of the SceneManager, so you don't have to keep a pointer to every SceneNode you create. You should hang on to the ones you use often though.

Things to Try

By now you should have a basic grasp of Entities, SceneNodes, and the SceneManager. I suggest starting with the code above and adding and removing Robots from the scene. Once you have done that, clear all the contents out of the _createScene method, and play with each of the following code segments:

Scale

You can scale the mesh by either setting the scale attribute or calling the scaleBy method in SceneNode. Note that this is one of the few places that we have had to significantly change the API from C++ Ogre. C++ Ogre has a scale function (which takes the current scale and multiplies it by the parameters) and the getScale/setScale pair (which sets the scale to a value). We always wrap get/set functions into an attribute of the same name. So in PyOgre, SceneNode.scale is the attribute which you use to set the scale. We have renamed C++ Ogre's scale function to scaleBy. Here is an example:

someNode.scale = 1, 1, 1   # sets the scale to the default value
        someNode.scale = 0.5, 1, 2 # sets the scale to be half in x, double in z

        someNode.scaleBy(1, 2, 3)  # same as 'someNode.scale = someNode.scale * (1, 2, 3)'

Try changing the values in scale and see what you get:

sceneManager = self.sceneManager
        sceneManager.ambientLight = (1, 1, 1)
 
        ent = sceneManager.createEntity("Robot", "robot.mesh")
        node = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
        node.attachObject(ent)
        node.scale = (.5, 1, 2)
        
        ent = sceneManager.createEntity("Robot2", "robot.mesh")
        node = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", (50, 0, 0))
        node.attachObject(ent)
        node.scale = (0.5, 0.2, 1)
        node.scaleBy(2, 10, 1)     # these two lines same as node.scale = (1, 2, 1)

Rotations



You can rotate the object by using the yaw, pitch, and roll methods using either Degree or Radian objects. Try changing the Degree amount and combining multiple transforms:

sceneManager = self.sceneManager
        sceneManager.ambientLight = (1, 1, 1)
 
        ent1 = sceneManager.createEntity("Robot", "robot.mesh")
        node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
        node1.attachObject(ent1)
        node1.yaw(ogre.Degree(-90))
 
        ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
        node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))
        node2.attachObject(ent2)
        node2.pitch(ogre.Degree(-90))
 
        ent3 = sceneManager.createEntity("Robot3", "robot.mesh")
        node3 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode3", ogre.Vector3(100, 0, 0))
        node3.attachObject(ent3)
        node3.roll(ogre.Degree(-90))

Note that you cannot call yaw, pitch, or roll without using either ogre.Degree or ogre.Radian to specify which you are using.

Conclusions

By this point you should have a very basic grasp of the SceneManager, SceneNode, and Entity classes. You do not have to be familiar with all of the functions that I have given reference to. Since these are the most basic objects, we will be using them very often. You will get more familiar with them after working through the next few tutorials.