Basic Tutorial 1
From Axiom
Beginner Tutorial 1: The SceneNode, Entity, and SceneManager constructs
Original version (for Ogre) by Clay Culver. Converted to Axiom by trejs, and touched up by William Lahti (xfurious-et-gmail-com).
Feedback on this tutorial is appreciated.
Prerequisites
This tutorial assumes you have knowledge of C# programming and are able to setup an Axiom application project (if you have trouble setting up your application, see the Setting Up An Application guide). No knowledge of Axiom is assumed for this tutorial outside of what is contained in the setup guide.
Introduction
In this tutorial I will be introducing you to the most basic Axiom constructs: SceneManager, SceneNode, and Entity objects. We aren't going to cover a large amount of code; instead I will be focusing on the general concepts for you to begin learning Axiom.
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.
ExampleApplication
We will be using a pre-constructed code base for this tutorial. Download the ExampleApplication.cs file and put it somewhere safe, we'll need it later. Alternatively, if you are using Visual Basic, download ExampleApplication.vb. While it would almost certainly be more educational to work with Axiom directly, rather than through a wrapper class, using the ExampleApplication class lets us get up and running a lot faster.
Dependencies
In order to compile and run our demo application, we need to make sure we have some important dependencies in the right places.
You will need to place the Axiom.dll library, at least one Axiom platform library (Axiom.Platforms.Win32.dll or Axiom.Platforms.SDL.dll), and at least one Axiom render system (Axiom.RenderSystems.DirectX9.dll or Axiom.RenderSystems.OpenGL.dll) in your project's build directory. In addition to these you will also need the following Axiom libraries:
- Axiom.Demos.dll
- Axiom.Plugins.CgProgramManager.dll
- Axiom.Plugins.ParticleFX.dll
- Axiom.SceneManagers.Bsp.dll
- Axiom.SceneManagers.Octree.dll
You may also need some or all of these libraries (depending on which platform/renderer combination which you are targeting, and which operating system you are developing on):
- cg.dll
- cgGL.dll
- DevIL.dll
- freeglut.dll
- glfw.dll
- ICSharpCode.SharpZipLib.dll
- ILU.dll
- ILUT.dll
- jpeg.dll
- libpng12.dll
- nvparse.dll
- OpenAL32.dll
- SDL.dll
- SDL_gfx.dll
- SDL_image.dll
- SDL_mixer.dll
- SDL_net.dll
- SDL_ttf.dll
- smpeg.dll
- Tao.Cg.dll
- Tao.DevIl.dll
- Tao.OpenGl.dll
- Tao.OpenGl.ExtensionLoader.dll
- Tao.OpenGl.Glu.dll
- Tao.Platform.Windows.dll
- wrap_oal.dll
- zlib1.dll
All of these libraries can be found in one of the packaged Axiom releases found in the Axiom Engine's SourceForge downloads section or built/found in the Subversion repository, although keep in mind that this tutorial specifically targets the Hobbiton release of Axiom, which is found in the archive axiom-0.7.2.0-bin.zip from sourceforge.net.
Creating, Configuring, Writing, and Building Your Project
EngineConfig.xml
In your favorite text editor, create a new file containing the following and save it as EngineConfig.xml in your project's output directory:
<?xml version="1.0" encoding="utf-8" ?>
<EngineConfig xmlns="http://tempuri.org/EngineConfig.xsd">
<FilePath type="Folder" src="Media/Textures"/>
<FilePath type="Folder" src="Media/Icons"/>
<FilePath type="Folder" src="Media/Fonts"/>
<FilePath type="Folder" src="Media/Meshes"/>
<FilePath type="Folder" src="Media/Skeletons"/>
<FilePath type="Folder" src="Media/Materials"/>
<FilePath type="Folder" src="Media/Overlays"/>
<FilePath type="Folder" src="Media/GpuPrograms"/>
<FilePath type="ZipFile" src="Media/Archives/Fresnel.zip"/>
<FilePath type="ZipFile" src="Media/Archives/Water.zip"/>
<FilePath type="ZipFile" src="Media/Textures/SkyBoxes.zip"/>
<FilePath type="ZipFile" src="Media/Archives/chiropteraDM.zip"/>
</EngineConfig>
NOTE: This assumes that you have the Media directory extracted from one of the Axiom releases, Subversion, or the Axiom media package (available at http://sourceforge.net/project/showfiles.php?group_id=84345) placed in your project's build directory. If you have it somewhere else on your hard drive, remember to change the paths in your EngineConfig.xml. If these paths are not set up correctly, most likely Axiom will end abruptly, leaving notice only in the log file (which, for these tutorials at least, will be in the 'AxiomExample.log' file.
Project Configuration
Create a console application in the IDE of your choice (or an 'exe' project using nant), and add the ExampleApplication.cs or ExampleApplication.vb file which you downloaded above to your project.
Next, you must add Axiom.dll as a reference to your project. In Microsoft Visual Studio, this is accomplished by right clicking References in the Solution Explorer, selecting Add Reference..., clicking the browse tab, navigating to your project's output folder, and selecting Axiom.dll. For nant users, you must add something like:
<include name="(path-to-build-directory)/Axiom.dll" />
Into the <references> element in the 'csc' or 'vbc' task of your nant buildfile.
Writing the Code
using System;
using Axiom;
using Axiom.Core;
using Axiom.Math;
namespace ExampleApplication
{
class Program
{
static void Main(string[] args)
{
AxiomTutorial app = new AxiomTutorial ();
app.Run();
}
}
class AxiomTutorial : ExampleApplication
{
protected override void CreateScene()
{
}
}
}
Imports System
Imports Axiom
Imports Axiom.Core
Imports Axiom.Math
Namespace ExampleApplication
Module Program
Public Sub Main(ByVal args As String())
Dim app As New AxiomTutorial
app.Run()
End Sub
Class AxiomTutorial
Inherits ExampleApplication
Protected Overrides Sub CreateScene()
End Sub
End Class
End Module
End Namespace
As you see, we are deriving AxiomTutorial from ExampleApplication. ExampleApplication contains all the necessary code to setup and run an Axiom application. Instead of modifying the ExampleApplication class directly, we will instead extend ExampleApplication by overriding some of its methods.
You should now be able to compile and run your application (use the escape key to exit).
Troubleshooting Hints
- First of all: If you see the Axiom Render Window (or it goes to full screen, depending on which option you selected from the command line) but it is black, give it time to load. On slower computers this can take some time. You'll know it is working when you see the Axiom logo and debug overlay in the render window.
- The code we've provided so far will send Axiom's log to the 'AxiomExample.log' file, which is usually the first place to look for problems. Lots of time can be saved by just looking at it!
- If you get compile errors about a missing Axiom assembly, make sure you added Axiom.dll as a reference as described above.
- If your program compiles but doesn't run, it's probably because you don't have all of the required DLLs (or some unnecessary ones) in your output directory, or your EngineConfig.xml is goofy. Make sure to check the AxiomExample.log file in your project's output directory for clues.
- Microsoft Windows-Specific Problems
- If you cannot compile the example because of errors about a missing Microsoft.DirectX assembly make sure that you have installed the latest DirectX SDK package.
- Linux/Mono-Specific Problems
- If you cannot run the example and your log lists errors related to DirectX: you should remove the DirectX-related assemblies from your output directory (at least for now), so that Axiom chooses the SDL equivalents instead.
- If you cannot run the example and your log lists errors related to DevIL, make sure you have installed the package which provides the native Linux DevIL library. For Ubuntu (and probably most Debian-based distributions), the package to install is 'libdevil1c2' and can be installed with: sudo apt-get install libdevil1c2
- If you see warnings/errors in the log about 'libCg.so.1' or so and you have an Nvidia card (these are not fatal), then you need to install the package containing the native libCg library, which for Ubuntu (and probably most Debian-based distributions), is 'nvidia-cg-toolkit' and can be installed using: sudo apt-get install nvidia-cg-toolkit
- If you are using the OpenGL RenderSystem and you see errors related to OpenGL or SDL libraries, make sure that you have chosen the correct 'config' file for your Linux distribution or family of distributions. For instance, the 'Tao.Sdl.dll.config.ubuntu' file provides the correct library mappings for Ubuntu, so renaming this to 'Tao.Sdl.dll.config' should solve errors related to resolving 'libSDL.so'.
If you're still running into problems, you can try searching the forums. It is likely your problem has happened to others many times. If this is a new issue, ask away. Make sure to provide relevant details from your .log file, exceptions, and/or error messages.
How Axiom Works
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 Axiom 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. We will cover more about other SceneManagers as we progress through the tutorials.
Entity Basics
An "Entity" is an object which is rendered on a scene. Entities are represented by 3D meshes which describe the geometry and materials of an object. 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, and Cameras, however, would not be entities.
One thing to note about Axiom is that it separates entities, lights, particles etc. from their location and orientation in the scene. 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 will not be 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 does it actually get 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 generate a light around him. You would do this by first creating a SceneNode, then creating an Entity for the character mesh, and attaching it to the SceneNode. Then you would create a Light object and attach it to the same SceneNode. SceneNodes may also be attached as children of 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 important 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.
Your first Axiom application
Now go back to the code we created earlier. Find the CreateScene method. 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 method and specifying what color we want. Note that the ColorEx constructor expects values for red, green, and blue in the range between 0 and 1. Add this line to CreateScene:
SceneManager.AmbientLight = new ColorEx(1, 1, 1);
SceneManager.AmbientLight = New ColorEx(1, 1, 1)
The next thing we need to do is create an Entity. We do this by calling the SceneManager's CreateEntity method:
Entity ent1 = SceneManager.CreateEntity("Robot", "robot.mesh");
Dim ent1 As Entity = SceneManager.CreateEntity("Robot", "robot.mesh")
Ok several questions should pop up. First of all, where did SceneManager come from, and what are the parameters we are calling the method with? The 'SceneManager' being used here is a property of the ExampleApplication class which your project's main class is inheriting. It returns a reference to Axiom's current SceneManager object. 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 ExampleApplication 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:
SceneNode node1 = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode");
Dim node1 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode")
This long statement first calls the RootSceneNode property of the current SceneManager. 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.
Finally, we need to attach the Entity to the SceneNode so that the Robot has a location to be rendered at:
node1.AttachObject(ent1);
node1.AttachObject(ent1)
And that's it! Compile and run your application. You should see a robot standing on the screen. Use the WASD keys to move, and the mouse to look around. Again, the Escape key exits the program.
Coordinates and Vectors
Before we go any further, we need to talk about screen coordinates and Axiom Vector objects. Axiom (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 the side closer to you ("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. Axiom makes no assumptions about how you orient your models. Each mesh that you load may have a different "starting direction" which it is facing.
Axiom 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 Axiom. 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 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 Axiom have default parameters for them. For example, the SceneNode.CreateChildSceneNode method in Axiom 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:
Entity ent2 = SceneManager.CreateEntity("Robot2", "robot.mesh");
SceneNode node2 = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", new Vector3(50, 0, 0));
node2.AttachObject(ent2);
Dim ent2 As Entity = SceneManager.CreateEntity("Robot2", "robot.mesh")
Dim node2 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", New 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 something slightly different. The second thing we have done is specified that the starting position will be 50 (positive) units away from the root SceneNode along the X axis. Which means that it would be rendered to the right of the other robot if you were looking down the Z axis. Remember that all SceneNode positions are relative to their parents. Compile and run the demo. Now there are two robots side-by-side.
Entities more in Depth
The Entity class is very extensive, and we will not be covering how to use all the members of the object here... just enough to get you started. There are a few immediately useful properties in Entity that I'd like to point out.
The first is IsVisible. You can set any Entity to be visible or not by simply setting the value of this property. If you need to hide an Entity, but later display it, then set it to false 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 will ever be loaded into memory, so you are not saving a lot of time 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 minor.
The Name property returns the name of Entity, and the ParentNode property returns the SceneNode that 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 with the Position property (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 the Scale method, and you can use the Yaw, Roll, and Pitch methods to rotate objects. The ResetOrientation method will reset all rotations done to the object. You can also use the Orientation property and the Rotate method for more advanced rotations. We will not be covering Quaternions until a much later tutorial though.
- You have already seen the AttachObject method. The following properties and methods are also useful if you are looking to manipulate the objects that are attached to a SceneNode: ObjectCount, GetObject (there are multiple overloads of this function), DetachObject (also multiple overloads) and DetachAllObjects. There are also a whole set of properties and methods for dealing with parent and child SceneNodes as well.
Relationships between related SceneNodes
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:
Entity ent1 = SceneManager.CreateEntity("Robot", "robot.mesh");
SceneNode node1 = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode");
node1.AttachObject(ent1);
Entity ent2 = SceneManager.CreateEntity("Robot2", "robot.mesh");
SceneNode node2 = node1.CreateChildSceneNode("RobotNode2", new Vector3(50, 0, 0));
node2.AttachObject(ent2);
Dim ent1 As Entity = SceneManager.CreateEntity("Robot", "robot.mesh")
Dim node1 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode")
node1.AttachObject(ent1)
Dim ent2 As Entity = SceneManager.CreateEntity("Robot2", "robot.mesh")
Dim node2 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", New Vector3(50, 0, 0))
node2.AttachObject(ent2)
If we change the 5th line from this:
SceneNode node2 = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", new Vector3(50, 0, 0));
Dim node2 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", New Vector3(50, 0, 0))
To this:
SceneNode node2 = node1.CreateChildSceneNode("RobotNode2", new Vector3(50, 0, 0));
Dim node2 As SceneNode = node1.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:
node2.Translate(new Vector3(10, 0, 10));
node2.Translate(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:
node1.Translate(new Vector3(25, 0, 0));
node1.Translate(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 retrieve SceneNodes by name using the GetSceneNode method of the SceneManager, so you don't have to keep a reference to every SceneNode you create. You should hang on to the ones you use often though, as this method does have an overhead.
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 calling the scale method in SceneNode. Try changing the values in scale and see what you get:
Entity ent = SceneManager.CreateEntity("Robot", "robot.mesh");
SceneNode node = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode");
node.AttachObject(ent);
node.Scale(new Vector3(0.5f, 1.0f, 2.0f));
ent = SceneManager.CreateEntity("Robot2", "robot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", new Vector3(50, 0, 0));
node.AttachObject(ent);
node.Scale(new Vector3(1.0f, 2.0f, 1.0f));
Dim ent1 As Entity = SceneManager.CreateEntity("Robot", "robot.mesh")
Dim node1 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode")
node1.AttachObject(ent1)
node1.Scale(New Vector3(0.5F, 1.0F, 2.0F))
Dim ent2 As Entity = SceneManager.CreateEntity("Robot2", "robot.mesh")
Dim node2 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", New Vector3(50, 0, 0))
node2.AttachObject(ent2)
node2.Scale(New Vector3(1.0F, 2.0F, 1.0F))
Rotations
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(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:
Entity ent = SceneManager.CreateEntity("Robot", "robot.mesh");
SceneNode node = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode", new Vector3(-100, 0, 0));
node.AttachObject(ent);
node.Yaw(-90);
ent = SceneManager.CreateEntity("Robot2", "robot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2");
node.AttachObject(ent);
node.Pitch(-90);
ent = SceneManager.CreateEntity("Robot3", "robot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode3", new Vector3(100, 0, 0));
node.AttachObject(ent);
node.Roll(-90);
Dim ent1 As Entity = SceneManager.CreateEntity("Robot", "robot.mesh")
Dim node1 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode")
node1.AttachObject(ent1)
node1.Yaw(-90)
Dim ent2 As Entity = SceneManager.CreateEntity("Robot2", "robot.mesh")
Dim node2 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", New Vector3(50, 0, 0))
node2.AttachObject(ent2)
node2.Pitch(-90)
Dim ent3 As Entity = SceneManager.CreateEntity("Robot2", "robot.mesh")
Dim node3 As SceneNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode2", New Vector3(50, 0, 0))
node2.AttachObject(ent3)
node2.Roll(-90)
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 properties and methods 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 Basic Tutorial 2 Cameras, Lights, and Shadows

