Beginner Tutorial 1: The SceneNode, Entity, and SceneManager constructs
Original version by Clay Culver
Initial C# edits by DigitalCyborg & ElectricBliss
Adapted to MOGRE by Adis
Ported to VB.NET by Aeauseth
Table of contents
- Getting Started
- How Ogre Works
- Your first Ogre application
- Coordinates and Vectors
- Adding another Object
- Entities more in Depth
- SceneNodes more in Depth
- Things to Try
- Final Note
This tutorial assumes you have knowledge of VB.nET programming and are able to setup and compile a Mogre application (if you have trouble setting up your application, see Mogre Basic Tutorial VB 0 for project setup). NO knowledge of Ogre is assumed for this tutorial outside of what is contained in the setup guide.
In this tutorial I will be introducing you to the most basic Ogre constructs: SceneManager, SceneNode, and Entity objects. The sample code includes keyboard & mouse handling, however we defer this topic until Tutorial 4. 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.
To speed things along copy the following code and paste it into Module1.vb. We will be discussing each section in more detail, and adding a few bits of code during this tutorial.
Imports Mogre Module Module1 Public myKeyboard As MOIS.Keyboard Public myMouse As MOIS.Mouse Public myCamera As Camera Public MyWindow As RenderWindow Public myTranslation As Vector3 = Vector3.ZERO Public Quitting As Boolean = False Public myRotating As Boolean = False Sub Main() Try 'Initialization of Ogre Root and RenderWindow Dim myRoot As Root = New Root("Plugins.cfg", "ogre.cfg", "ogre.log") 'Show Ogre Rendering Subsystem setup dialog box If Not myRoot.RestoreConfig Then If Not myRoot.ShowConfigDialog Then Exit Sub End If End If 'Create an Ogre render window MyWindow = myRoot.Initialise(True, "Application") AddHandler myRoot.FrameStarted, AddressOf FrameStarted 'Create Ogre SceneManager & Set AmbientLight to bright white Dim mySceneManager As SceneManager = myRoot.CreateSceneManager(SceneType.ST_GENERIC) mySceneManager.AmbientLight = New ColourValue(1, 1, 1) 'Read Resources Dim cf As New ConfigFile cf.Load("resources.cfg", vbTab + ":=", True) Dim seci As ConfigFile.SectionIterator = cf.GetSectionIterator Dim secName As String, typeName As String, archName As String While (seci.MoveNext()) secName = seci.CurrentKey Dim settings As ConfigFile.SettingsMultiMap = seci.Current For Each pair As KeyValuePair(Of String, String) In settings typeName = pair.Key archName = pair.Value ResourceGroupManager.Singleton.AddResourceLocation(archName, typeName, secName) Next End While ResourceGroupManager.Singleton.InitialiseAllResourceGroups() 'Create Robot Dim myRobot As Entity = mySceneManager.CreateEntity("Robot", "robot.mesh") 'Create Node Dim myNode As SceneNode = mySceneManager.RootSceneNode.CreateChildSceneNode("RobotNode") 'Attach Robot to Node myNode.AttachObject(myRobot) '2nd Robot '3rd Robot 'Create Camera myCamera = mySceneManager.CreateCamera("Camera") myCamera.SetPosition(0, 0, 500) myCamera.LookAt(0, 0, 0) myCamera.NearClipDistance = 5 myCamera.FarClipDistance = 5000 'Viewport Dim myViewport As Viewport = myWindow.AddViewport(myCamera) 'Keyboard Dim windowHnd As Integer MyWindow.GetCustomAttribute("WINDOW", windowHnd) Dim myInputManager As MOIS.InputManager = MOIS.InputManager.CreateInputSystem(windowHnd) myKeyboard = myInputManager.CreateInputObject(MOIS.Type.OISKeyboard, True) AddHandler myKeyboard.KeyPressed, AddressOf InputClass.KeyPressed AddHandler myKeyboard.KeyReleased, AddressOf InputClass.KeyReleased 'Mouse myMouse = myInputManager.CreateInputObject(MOIS.Type.OISMouse, True) AddHandler myMouse.MouseMoved, AddressOf InputClass.MouseMovedListener AddHandler myMouse.MousePressed, AddressOf InputClass.MousePressedListener AddHandler myMouse.MouseReleased, AddressOf InputClass.MouseReleasedListener 'Start rendering myRoot.StartRendering() Catch ex As System.Runtime.InteropServices.SEHException If OgreException.IsThrown Then MsgBox(OgreException.LastException.FullDescription, MsgBoxStyle.Critical, _ "An Ogre exception has occured!") Else MsgBox(ex.ToString, "An error has occured") End If End Try End Sub Public Function FrameStarted(ByVal e As FrameEvent) As Boolean 'Capture buffered input myKeyboard.Capture() myMouse.Capture() 'Handle player/camera movement InputClass.ProcessKeyboard() 'Move camera myCamera.Position += myCamera.Orientation * myTranslation * e.timeSinceLastFrame If Quitting Then Return False 'This is the only way to stop "myRoot.StartRendering()" End If Return True End Function Public Class InputClass Const TRANSLATE As Single = 200 Const ROTATE As Single = 0.003 Shared Function KeyPressed(ByVal e As MOIS.KeyEvent) As Boolean Select Case e.key Case MOIS.KeyCode.KC_ESCAPE Quitting = True End Select Return Nothing End Function Shared Function KeyReleased(ByVal e As MOIS.KeyEvent) As Boolean 'This function is just a placeholder 'It is unlikely you will ever use this 'Tyipcally you either process unbuffered keyboard input (as in ProcessKeyboard) 'or you process buffered Keypress Return Nothing End Function Shared Sub ProcessKeyboard() 'Clear previous translation myTranslation.z = 0 myTranslation.x = 0 myTranslation.y = 0 If myKeyboard.IsKeyDown(MOIS.KeyCode.KC_UP) Or _ myKeyboard.IsKeyDown(MOIS.KeyCode.KC_W) Then myTranslation.z += -TRANSLATE End If If myKeyboard.IsKeyDown(MOIS.KeyCode.KC_S) Or _ myKeyboard.IsKeyDown(MOIS.KeyCode.KC_DOWN) Then myTranslation.z += TRANSLATE End If If myKeyboard.IsKeyDown(MOIS.KeyCode.KC_A) Or _ myKeyboard.IsKeyDown(MOIS.KeyCode.KC_LEFT) Then myTranslation.x += -TRANSLATE End If If myKeyboard.IsKeyDown(MOIS.KeyCode.KC_D) Or _ myKeyboard.IsKeyDown(MOIS.KeyCode.KC_RIGHT) Then myTranslation.x += TRANSLATE End If If myKeyboard.IsKeyDown(MOIS.KeyCode.KC_Q) Or _ myKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGUP) Or _ myKeyboard.IsKeyDown(MOIS.KeyCode.KC_SPACE) Then myTranslation.y += TRANSLATE End If If myKeyboard.IsKeyDown(MOIS.KeyCode.KC_Z) Or _ myKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGDOWN) Then myTranslation.y += -TRANSLATE End If End Sub Shared Function MouseMovedListener(ByVal e As MOIS.MouseEvent) As Boolean 'Only rotate camera if Right mouse button is down If myRotating Then myCamera.Yaw(e.state.X.rel * -ROTATE) myCamera.Pitch(e.state.Y.rel * -ROTATE) End If End Function Shared Function MousePressedListener( _ ByVal e As MOIS.MouseEvent, ByVal id As MOIS.MouseButtonID) As Boolean 'Checking to see if Right Mouse button is down If e.state.ButtonDown(MOIS.MouseButtonID.MB_Right) Then myRotating = True End If End Function Shared Function MouseReleasedListener( _ ByVal e As MOIS.MouseEvent, ByVal id As MOIS.MouseButtonID) As Boolean If Not e.state.ButtonDown(MOIS.MouseButtonID.MB_Right) Then myRotating = False End If End Function End Class End Module
Compile and run the program (F5). If you get an Ogre splash screen, pick Direct3D9 Rendering and set Full Screen to "No". You should end up with one black screen with some status messages scrolling by, and another screen with a Robot. If you are having trouble with this (or recieve an exception during startup, go back and double check you have followed all of the instructions in Mogre Basic Tutorial VB 0.
Throughout this series of tutorials I will be linking to the Ogre API reference. You should keep in mind that Mogre is a .NET binding of the C++ Ogre graphics engine, and as such the functions have been changed to match the .NET coding standards. Functions begin with an initial caps instead of lower case letters. Get and set functions have been removed entirely in favor of properties. For example, the C++ functions "setAmbientLight" and "getAmbientLight" have been changed to just be the "AmbientLight" property. When in doubt, use intellisense to figure out what a function call should be.
A broad topic. We will start with SceneManagers and work our way to Entities and SceneNodes. These three classes are the fundamental building blocks of Ogre applications.
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 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.
An Entity is one of the types of object that you can render on a scene. 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 entities.
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.
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 to which all other SceneNodes are attached.
Now go back to the code we created earlier. The first thing we want to do is create a SceneManager. Then we set the ambient light for the scene so that we can see what we are doing. We do this by setting the AmbientLight property 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. You will see the following code after the 'Create an Ogre render window' section.
'Create Ogre SceneManager & Set AmbientLight to bright white Dim mySceneManager As SceneManager = myRoot.CreateSceneManager(SceneType.ST_GENERIC) mySceneManager.AmbientLight = New ColourValue(1, 1, 1)
Resources are meshes, materials, textures and much more (scripts, packs, etc.). To use resources, you first indicate where to find them. The natural way to do it in Ogre is to load a configuration file (resources.cfg). You do not need to understand how it works for the moment. In a later tutorial we'll detail the concepts involved in this code.
'Read Resources Dim cf As New ConfigFile cf.Load("resources.cfg", vbTab + ":=", True) Dim seci As ConfigFile.SectionIterator = cf.GetSectionIterator Dim secName As String, typeName As String, archName As String While (seci.MoveNext()) secName = seci.CurrentKey Dim settings As ConfigFile.SettingsMultiMap = seci.Current For Each pair As KeyValuePair(Of String, String) In settings typeName = pair.Key archName = pair.Value ResourceGroupManager.Singleton.AddResourceLocation(archName, typeName, secName) Next End While ResourceGroupManager.Singleton.InitialiseAllResourceGroups()
We create a Robot Entity that will actually gets displayed. We do this by calling the SceneManager's CreateEntity method:
'Create Robot Dim myRobot As Entity = mySceneManager.CreateEntity("robot.mesh")
Ok several questions should pop up. The first parameter to SceneManager's 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. The mesh was loaded in the Read Resources section. You typically have to create or download your own meshes, however we are using a mesh included in the OgreSDK.
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:
'Create Node Dim myNode As SceneNode = mySceneManager.RootSceneNode.CreateChildSceneNode("RobotNode")
This statement first gets the RootSceneNode of the current SceneManager, and then it 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. We can omit the name parameter for SceneNodes if we choose to do so, and a default name will be assigned to the node.
We need to attach the Entity to the SceneNode so that the Robot has a location to be rendered at:
'Attach Robot to Node myNode.AttachObject(myRobot)
We need a camera through which the scene can be viewed.
'Create Camera Dim myCamera As Camera = mySceneManager.CreateCamera("Camera") myCamera.SetPosition(0, 0, 500) myCamera.LookAt(0, 0, 0) myCamera.NearClipDistance = 5 myCamera.FarClipDistance = 5000
The final step is to tell the Camera that it must display in the RenderWindow. For that we must create a viewport attached to the camera.
'Viewport Dim myViewport As Viewport = myWindow.AddViewport(myCamera)
And that's it! Run your application in debug mode. You should see a robot standing on the screen. Use WASD to move around. You can also hold down the right mouse button to pivot the camera. Again the details on how keyboard & mouse input will be discussed in Tutorial 4
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.
Now that you understand how the coordinate systems work, we can go back to our code. In the three 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 method 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). Let's create another SceneNode, but this time we'll specify the starting location to be something other than the origin. Find the 2nd Robot section and add the following code:
'2nd Robot Dim myRobot2 As Entity = mySceneManager.CreateEntity("Robot2", "robot.mesh") Dim myNode2 As SceneNode = myNode.CreateChildSceneNode("RobotNode2", New Vector3(50, 0, 0)) myNode2.AttachObject(myRobot2)
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 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). Run the program in debug mode. Now there are two robots side-by-side.
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 methods and properties in Entity that I'd like to point out.
The first is Entity::setVisible and Entity::isVisible (which is wrapped into Entity.Visible in Mogre). You can set any Entity to be visible or not by simply setting "someEntity.Visible = 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 yourself much by trying to save them. The only thing you really save is the creation and destruction costs for the Entity object itself, which is relatively low.
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 with the Position attribute (this is always relative to the parent SceneNode). You can move the object relative to its current position by adding and subtracting from this Property.
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 function. 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 property, 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 properties are also useful if you are looking to manipulate the objects that are attached to a SceneNode: NumAttachedObjects, GetAttachedObject (there are multiple versions of this function), DetachObject (also multiple versions), DetachAllObjects. 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:
'Create Robot Dim myRobot As Entity = mySceneManager.CreateEntity("Robot", "robot.mesh") 'Create Node Dim myNode As SceneNode = mySceneManager.RootSceneNode.CreateChildSceneNode("RobotNode") 'Attach Robot to Node myNode.AttachObject(myRobot) '2nd Robot Dim myRobot2 As Entity = mySceneManager.CreateEntity( "Robot2", "robot.mesh") Dim myNode2 As SceneNode = mySceneManager.RootSceneNode.CreateChildSceneNode( _ "RobotNode2", New Vector3(50, 0, 0)) myNode2.AttachObject(myRobot2)
If we change the 5th line from this:
Dim myNode2 As SceneNode = mySceneManager.RootSceneNode.CreateChildSceneNode( _ "RobotNode2", New Vector3(50, 0, 0))
Dim myNode2 As SceneNode = myNode.CreateChildSceneNode("RobotNode2", New Vector3(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:
myNode2.Position += New Vector3(10, 0, 10)
The following code would move RobotNode, and since RobotNode2 is a child of RobotNode, RobotNode2 would be moved as well:
myNode.Position += New Vector3(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. Let's 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 its parent.
Now let's figure out where these things really are. Start at the root SceneNode. Its 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.
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 try out some of the following:
You can scale the mesh by calling the scale method in SceneNode. Try changing the values in scale and see what you get (place this at the end of your CreateScene method):
myNode.Scale(0.5, 1, 2) myNode2.Scale(1, 2, 1)
You can rotate the object by using the Yaw, Pitch, and Roll methods using either Degree or Radian objects. Pitch is rotation around the x axis, yaw is around the y axis, and roll is around the z axis. Using your right hand as a guide: point your thumb to the axis, the other fingers points to the positive angles. For example, pitch(Degree(90)), point your thumb to right, the other fingers show the direction of the rotation. Imagine closing a box lid.
Try changing the Degree amount and combining multiple transforms:
'3rd Robot Dim myRobot3 As Entity = mySceneManager.CreateEntity("Robot3", "robot.mesh") Dim myNode3 As SceneNode = mySceneManager.RootSceneNode.CreateChildSceneNode( _ "RobotNode3", New Vector3(100, 0, 0)) myNode3.AttachObject(myRobot3) myNode.Pitch(New Degree(90)) myNode2.Yaw(New Degree(90)) myNode3.Roll(New Degree(90))
By this point you should have a very basic grasp of the SceneManager, SceneNode, and Entity classes. You have also been esposed to mouse & keyboard input. 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.
- Proceed to Mogre Basic Tutorial VB 2 Cameras, Lights, and Shadows