Views
Intermediate Tutorial 1
From Axiom
Animation, Walking Between Points, and Basic Quaternions
- Original Version By Clay Culver.
- Initial C# changes and port to OgreDotNet by DigitalCyborg
- Port to Axiom Hobbiton by patientfox
Note: This was written for Axiom Hobbiton 7.3.0, and is known to compile against it. Any problems you encounter while working with this tutorial should be posted to the Hobbiton Forum.
This tutorial has been testing with Axiom Crickhollow (REV 1216) and was found to be working with a few fixes, listed below:
- Change the 'using ExampleApplication;' statement to 'using ExampleApplicationCrickhollow', if you're using the ExampleApplicationCrickhollow.cs file available on the wiki.
- In the inheritance for the MoveDemoAplication, change it to just 'ExampleApplication'
- Rename the FrameStarted() method to OnFrameStarted()
- Add a call to UpdateInput() in the OnFrameStarted() method
- Sacrifice a goat, pray for mercy.
Please report any issues to the [Crickhollow Forum].
Contents |
Introduction
In this tutorial we will be covering how to take an Entity, animate it, and have it walk between predefined points. This will also cover the basics of Quaternion rotation by showing how to keep the Entity facing the direction it is moving.
Prerequisites
This tutorial will assume that you already know how to set up an Axiom project and make it compile successfully. If you don't already have a working template project, consult Basic Tutorial 1 for help. This tutorial also makes use of .NETs LinkedList<T> data structure. While no prior knowledge of how to use it, you should at least know what .NET Generics are.
If you are not familiar with .NET Generics, the author recommends google and/or this tutorial. This will save you a lot of time in the future. As an added bonus, the above-linked tutorial discusses LinkedList<T>, which is essential a doubly-linked list aka a two-ended queue (This is, a queue/list that allows you to add/remove items from the beginning/end of the queue/list).
Getting Started
First, you need to create a new project for the demo. Add a file called “MoveDemo.cs” to the project, and add this to it:
using System;
using System.Collections;
using System.Collections.Generic;
using Axiom;
using Axiom.Core;
using Axiom.Math;
using Axiom.Graphics;
using Axiom.Animating;
using ExampleApplication;
namespace AxiomTutorialIntermediate1
{
class Program
{
static void Main(string[] args)
{
MoveDemoApplication app = new MoveDemoApplication();
app.Run();
}
}
class MoveDemoApplication : ExampleApplication.ExampleApplication
{
protected Entity _robotEnt = null; // entity we are animating
protected SceneNode _robotNode = null; // the scenenode of the object we're animating
protected AnimationState _robotAnimationState = null; // animationstate of the moving object
protected float _distanceToNextWaypoint = 0.0f; // the distance the object has left to travel
protected Vector3 _directionOfTravel = Vector3.Zero; // the direction the object is moving
protected Vector3 _nextWaypoint = Vector3.Zero; // the destination that the object is moving towards
protected LinkedList<Vector3> _waypointList = null; // the doubly linked list containing the waypoints
protected float _robotWalkSpeed = 35.0f; // the speed at which the object is moving
protected bool _isRobotWalking = false; // Whether or not the object is moving
protected float _timeSinceLastFrame = 0.0f;
protected override void CreateScene()
{
Entity ent;
SceneNode node;
}
protected override void CreateCamera()
{
}
protected override void CreateViewports()
{
}
protected bool nextLocation()
{
return true;
}
protected override void FrameStarted(object source, FrameEventArgs e)
{
if (!RenderWindow.IsActive) return;
_timeSinceLastFrame = e.TimeSinceLastFrame;
UpdateOverlay(source, e);
}
}
}
Be sure you can compile this code before continuing.
Setting up the Scene
Before we begin, notice that we have already defined some variables in MoveDemoApplication. The _robotEntity will hold the entity we create, _robotNode will hold the node we create, _robotAnimationState will hold the animation state of the object, _distanceToNextWaypoint will contain the distance from our location to the next waypoint, _directionOfTravel will hold the direction the object is moving, _nextWaypoint will hold the next waypoint location, _waypointList will contain all the points we wish the object to walk to, _robotWalkSpeed will hold our speed and _isRobotWalking will indicate whether or not we are moving.
Go to the CreateScene() method and add the following code to it. First we are going to set the ambient light to full so that we can see objects we put on the screen.
// Set the default lighting
SceneManager.AmbientLight = ColorEx.White;
We also need to set up our Viewport. Go to the CreateCamera() method and add this code:
Then go to the CreateViewports() method and add the following block of code:
// associate the viewport with our camera and set the background color to black.
Viewport vp = RenderWindow.AddViewport(Camera);
vp.BackgroundColor = ColorEx.Black;
Camera.AspectRatio = (float)(vp.ActualWidth / vp.ActualHeight);
If it isn't obvious to you why we have to use the Viewport to set the background, please consult Basic Tutorial 2.
Next we will create a Robot on the screen so that we can play with him. To do this we will create the entity for the Robot, then create a SceneNode for him to dangle from.
// Create the Robot entity
_robotEnt = SceneManager.CreateEntity("Robot", "robot.mesh");
// Create the Robot's SceneNode
_robotNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode",
new Vector3(0.0f, 0.0f, 25.0f));
_robotNode.AttachObject(_robotEnt);
This all should be very basic, so I will not go into detail about any of it. In the next chunk of code, we are going to tell the robot where he needs to be moved to. For those of you who don't know anything about .NETs LinkedList, it is an efficient (?) implementation of a doublely linked list aka that we can use as a two ended queue. We will only be using a few of its methods. The AddFirst() and AddLast() methods put items at the front and back of the list, respectively. The First() and Last() methods return the items at the front and back of the list, respectively. The RemoveFirst() and RemoveLast() methods remove the items from the front and back of the list, respectively. Finally, the Count property returns the number of entries in the list. This code creates the list, then adds three Vectors to it. We will use these later when we make the robot move. Add this to CreateScene():
// Create the walking list
_waypointList = new LinkedList<Vector3>();
_waypointList.AddLast(new Vector3(550.0f, 0.0f, 50.0f));
_waypointList.AddLast(new Vector3(-100.0f, 0.0f, -200.0f));
_waypointList.AddLast(new Vector3(0.0f, 0.0f, 25.0f));
Next, we want to place some objects on the scene to show where the robot is supposed to be moving. This will allow us to see the robot moving with respect to other objects on the screen. Notice the negative Y component to their position. This puts the objects under where the robot is moving to, and he will stand on top of them when he gets to the correct place.
// Create knot objects so we can see movement
ent = SceneManager.CreateEntity("Knot1", "knot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("Knot1Node",
new Vector3(0.0f, -10.0f, 25.0f));
node.AttachObject(ent);
node.Scale(new Vector3(0.1f, 0.1f, 0.1f));
//
ent = SceneManager.CreateEntity("Knot2", "knot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("Knot2Node",
new Vector3(550.0f, -10.0f, 50.0f));
node.AttachObject(ent);
node.Scale(new Vector3(0.1f, 0.1f, 0.1f));
//
ent = SceneManager.CreateEntity("Knot3", "knot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("Knot3Node",
new Vector3(-100.0f, -10.0f, -200.0f));
node.AttachObject(ent);
node.Scale(new Vector3(0.1f, 0.1f, 0.1f));
Finally, we want to set the camera to a good viewing point to see this from. We will move the camera to get a better position. Add this code to the CreateCamera() method:
Camera = SceneManager.CreateCamera("PlayerCam");
Camera.Position = new Vector3(90.0f, 280.0f, 535.0f);
Camera.LookAt(new Vector3(0.0f, 0.0f, 25.0f));
Camera.Near = 5.0f;
Now compile and run the code. You should see something like a robot standing on top of a knot. If you aren't satisfied with the current camera position, move the camera around with the mouse and W,A,S and D keys until you get a better view.
Animation
We are now going to setup some basic animation. Animation in OGRE is very simple. To do this, you need to get the AnimationState from the Entity object, set its options, and enable it. This will make the animation active, but you will also need to add time to it after each frame in order for the animation to run. We'll take this one step at a time. First, add this to the bottom of CreateScene.
// Set idle animation
_robotAnimationState = _robotEnt.GetAnimationState("Idle");
_robotAnimationState.Loop = true;
_robotAnimationState.IsEnabled = true;
The first line of code gets the AnimationState out of the entity. In the second line we call SetLoop( true ), which makes the animation loop over and over. For some animations (like the death animation), we would want to set this to false instead. The third line actually enables the Animation. But wait...where did we get “Idle” from? How did this magic string slip in there? Every mesh has their own set of Animations defined for them. In order to see all of the Animations for the particular mesh you are working on, you need to download the OgreMeshViewer and view the mesh from there.
Now, if we compile and run the demo we see...nothing has changed. This is because we need to update the animation state with a time every frame. Find the FrameStarted method, and add this line of code to the end of the method:
//Update the Animation State.
_robotAnimationState.AddTime(_timeSinceLastFrame);
float move = 0.0f;
The reason for the addition of a 'move' float value will become evident later in the tutorial. Now build and run the application. You should see a robot performing his idle animation standing in place. pretty spiffy huh?
Moving the Robot
Now we are going to perform the slightly tricky task of making the robot walk from waypoint to waypoint. Before we begin I would like to re-describe some the variables that we are storing in the class. We are going to use 4 variables to accomplish the task of moving the robot. The direction the robot is moving is stored in _directionOfTravel. The robot's current destination is stored in _nextWaypoint. The distance the robot has left to travel is stored in _distanceToNextWaypoint. Last but not least, the robot's speed is stored in _robotWalkSpeed.
Since the variables are already initialized we can use them to set the robot in motion. To make the robot move, we simply tell it to change animations. However, we only want to start the robot moving if there is another location to move to. The nextLocation() method is used for this.
Add this code to FrameStarted() method just above _robotAnimationState.AddTime(_timeSinceLastFrame) which should be the last expression in the method:
if (!_isRobotWalking)
//either we've not started walking or reached a way point
{
//check if there are places to go
if (nextLocation())
{
//Start the walk animation
_robotAnimationState = _robotEnt.GetAnimationState("Walk");
_robotAnimationState.Loop = true;
_robotAnimationState.IsEnabled = true;
_isRobotWalking = true;
//Update the destination using the walklist.
}//if(nextLocation())
}
If you compile and run the code right now, the robot will walk in place. This is because the robot starts out with a mWalking = false and our nextLocation function always returns true. In later steps we will be adding a bit more intelligence to the nextLocation function. Note that we also left a placeholder for the code to update the Robot's destination. we'll get to that later too :)
Now we are going to actually move the robot in the scene. To do this we need to have him move a small bit every frame. We will be adding the following code just after our previous if statement and just above the return line of the method. This code will handle the case when the robot is actually moving; (mWalking == true)
else //we're in motion
{
//determine how far to move this frame
move = _robotWalkSpeed * _timeSinceLastFrame;
_distanceToNextWaypoint -= move;
}
Now, we need to check and see if we've arrived at the waypoint. That is, if _distanceToNextWaypoint is now less than or equal to zero, we need to set the point and setup the move to the next point. Note that we are setting _directionOfTravel to the ZERO vector and that we are setting _isRobotWalking to false.(the robot stops at each waypoint, then checks to see if it has another waypoint).
if (_distanceToNextWaypoint <= 0.0f)
{
//set our node to the destination we've just reached & reset direction to 0
_robotNode.SetPosition(mDestination);
_directionOfTravel = Vector3.Zero;
_isRobotWalking = false;
}//if(mDistance <= 0.0f)
else
{
//movement code goes here
//Rotation code goes here
}
Now that we have moved to the point, we need to setup the motion to the next point. Once we know if we need to move to another point or not, we can set the appropriate animation; walking if there is another point to go to and idle if there are no more destination points. This is a simple matter of setting the Idle animation if there are no more locations. update Framestarted just below the close of the if statement
} //if(nextLocation())
else //nowhere to go. set the idle animation. (or Die)
{
_robotAnimationState = _robotEnt.GetAnimationState("Idle");
//_robotAnimationState = _robotEnt.GetAnimationState("Die");
//_robotAnimationState.Loop = false;
}
Note that we have no need to set the walking animation again if there are more points in the queue to walk to. Since the robot is already walking there is no reason to tell him to do so again. However, if the robot needs to go to another point, then we need to rotate him to face that point. For now we leave a placeholder comment in the else clause; remember this spot as we will come back to it later.
This takes care of when we are very close to the target position. Now we need to handle the normal case, when we are just on the way to the position but we're not there yet. To do that we will translate the robot in the direction we are traveling, and move it by the amount specified by the move variable. This is accomplished by adding the following code:
else
{
//movement code goes here
_robotNode.Translate(_directionOfTravel * move);
//Rotation code goes here
}
We are almost done. Our code now does everything except set up the variables required for movement. If we can properly set the movement variables our Robot will move like he is supposed to. Find the nextLocation() function. This function returns false when we run out of points to go to. This will be the first of code line in our function. (Note you should leave the return true statement at the bottom of the function.)
if (_waypointList.Count == 0) return false;
Now we need to set the movement variables. We'll do this where we left this placeholder earlier
//Update the destination using the walklist.
First we will extract the destination vector from the front of the LinkedList and then add the LinkedListNode that held it to the end of the list. This will make the robot keep walking around from point to point. We will set the direction vector by subtracting the SceneNode's current position from the destination. We have a problem though. Remember how we multiplied _directionOfTravel by the move amount in FrameStarted()? If we do this, we need the direction vector to be a unit vector (that is, it's length equals one). The normalise function does this for us, and returns the old length of the vector. Handy that, since we need to also set the distance to the destination.
//Update the destination using the walklist.
LinkedListNode<Vector3> tmp; //temporary listNode
_nextWaypoint = _waypointList.First.Value; //get the next destination.
tmp = _waypointList.First; //save the node that held it
_waypointList.RemoveFirst(); //remove that node from the front of the list
_waypointList.AddLast(tmp); //add it to the back of the list.
//update the direction and the distance
_directionOfTravel = _nextWaypoint - _robotNode.Position;
_distanceToNextWaypoint = _directionOfTravel.Normalize();
Now compile and run the code. It works! Mostly... The robot now walks to all the points, but he is always facing the Vector3.UnitX direction (his default). We will need to change the direction he is facing when he is moving towards points.
What we need to do is get the direction the Robot is facing, and use the rotate function to rotate the object in the right position. Insert the following code where we left the following placeholder comment earlier.
//Rotation code goes here
The first line gets the direction the Robot is facing. The second line builds a Quaternion representing the rotation from the current direction to the destination direction. The third line actually rotates the Robot.
//Rotation code goes here
Vector3 src = _robotNode.Orientation * Vector3.UnitX;
Quaternion quat = src.GetRotationTo(_directionOfTravel);
_robotNode.Rotate(quat);
A lot is going on in that small chunk code! A few questions pop into mind. The most per tenant is: "What is a Quaternion?" Basically, Quaternions are representations of rotations in 3 dimensional space. They are used to keep track of how the object is positioned in space. In the first line we call the GetOrientation() method, which returns a vector representing the way the Robot is currently facing. By multiplying it by the UnitX vector, we obtain the direction the robot is currently facing, which we store in src. In the second line, the GetRotationTo() method gives us a Quaternion that represents the rotation from the direction the Robot is facing to the direction we want him to be facing. In the third line we use the quaternion to rotate the node so that it faces the new orientation.
There is only one problem with the code we have created. There is a special case where SceneNode.Rotate() will fail. If we are trying to turn the Robot 180 degrees, the rotate code will bomb with a divide by zero error. In order to fix that, we will test to see if we are performing a 180 degree rotation. If so, we will simply yaw the Robot by 180 degrees instead of using rotate. To do this, delete the three lines we just put in and replace them with this:
//Rotation code goes here
Vector3 src = _robotNode.Orientation * Vector3.UnitX;
if ((1.0f + src.Dot(_directionOfTravel)) < 0.0001f)
{
_robotNode.Yaw(180.0f);
}
else
{
Quaternion quat = src.GetRotationTo(_directionOfTravel);
_robotNode.Rotate(quat);
}
All of this should now be self explanatory except for what is wrapped in that if statement. If two unit vectors oppose each other (that is, the angle between them is 180 degrees), then their dot product will be -1. So, if we dotProduct the two vectors together and the result equals 1.0f, then we need to yaw by 180 degrees, otherwise we use rotate instead. Why do I add 1.0f and check to see if it is less than 0.0001f? Don't ever forget about floating point rounding error. You should never directly compare two floating point numbers. Finally, note that in this case the dot product of these two vectors will fall in the range [-1, 1]. In case it is not abundantly clear, you need to know at minimum basic linear algebra to do graphics programming! At the very least you should review the Quaternion and Rotation Primer and consult a book on basic vector and matrix operations.
Now our code is complete! Compile and run the demo to see the Robot walk the points he was given.
Here's the full code
using System;
using System.Collections;
using System.Collections.Generic;
using Axiom;
using Axiom.Core;
using Axiom.Math;
using Axiom.Graphics;
using Axiom.Animating;
using ExampleApplication;
namespace AxiomTutorialIntermediate1
{
class Program
{
static void Main(string[] args)
{
MoveDemoApplication app = new MoveDemoApplication();
app.Run();
}
}
class MoveDemoApplication : ExampleApplication.ExampleApplication
{
protected Entity _robotEnt = null; // entity we are animating
protected SceneNode _robotNode = null; // the scenenode of the object we're animating
protected AnimationState _robotAnimationState = null; // animationstate of the moving object
protected float _distanceToNextWaypoint = 0.0f; // the distance the object has left to travel
protected Vector3 _directionOfTravel = Vector3.Zero; // the direction the object is moving
protected Vector3 _nextWaypoint = Vector3.Zero; // the destination that the object is moving towards
protected LinkedList<Vector3> _waypointList = null; // the doubly linked list containing the waypoints
protected float _robotWalkSpeed = 35.0f; // the speed at which the object is moving
protected bool _isRobotWalking = false; // Whether or not the object is moving
protected float _timeSinceLastFrame = 0.0f;
protected override void CreateScene()
{
Entity ent;
SceneNode node;
// Set the default lighting
SceneManager.AmbientLight = ColorEx.White;
// Create the Robot entity
_robotEnt = SceneManager.CreateEntity("Robot", "robot.mesh");
// Create the Robot's SceneNode
_robotNode = SceneManager.RootSceneNode.CreateChildSceneNode("RobotNode",
new Vector3(0.0f, 0.0f, 25.0f));
_robotNode.AttachObject(_robotEnt);
// Create the walking list
_waypointList = new LinkedList<Vector3>();
_waypointList.AddLast(new Vector3(550.0f, 0.0f, 50.0f));
_waypointList.AddLast(new Vector3(-100.0f, 0.0f, -200.0f));
_waypointList.AddLast(new Vector3(0.0f, 0.0f, 25.0f));
// Create knot objects so we can see movement
ent = SceneManager.CreateEntity("Knot1", "knot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("Knot1Node",
new Vector3(0.0f, -10.0f, 25.0f));
node.AttachObject(ent);
node.Scale(new Vector3(0.1f, 0.1f, 0.1f));
//
ent = SceneManager.CreateEntity("Knot2", "knot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("Knot2Node",
new Vector3(550.0f, -10.0f, 50.0f));
node.AttachObject(ent);
node.Scale(new Vector3(0.1f, 0.1f, 0.1f));
//
ent = SceneManager.CreateEntity("Knot3", "knot.mesh");
node = SceneManager.RootSceneNode.CreateChildSceneNode("Knot3Node",
new Vector3(-100.0f, -10.0f, -200.0f));
node.AttachObject(ent);
node.Scale(new Vector3(0.1f, 0.1f, 0.1f));
_robotAnimationState = _robotEnt.GetAnimationState("Idle");
_robotAnimationState.Loop = true;
_robotAnimationState.IsEnabled = true;
}
protected override void CreateCamera()
{
Camera = SceneManager.CreateCamera("PlayerCam");
Camera.Position = new Vector3(90.0f, 280.0f, 535.0f);
Camera.LookAt(new Vector3(0.0f, 0.0f, 25.0f));
Camera.Near = 5.0f;
}
protected override void CreateViewports()
{
Viewport vp = RenderWindow.AddViewport(Camera);
vp.BackgroundColor = ColorEx.Black;
Camera.AspectRatio = (float)(vp.ActualWidth / vp.ActualHeight);
}
protected bool nextLocation()
{
if (_waypointList.Count == 0) return false;
return true;
}
protected override void FrameStarted(object source, FrameEventArgs e)
{
if (!RenderWindow.IsActive) return;
_timeSinceLastFrame = e.TimeSinceLastFrame;
UpdateOverlay(source, e);
//Update the Animation State.
_robotAnimationState.AddTime(_timeSinceLastFrame);
float move = 0.0f;
if (!_isRobotWalking)
//either we've not started walking or reached a way point
{
//check if there are places to go
if (nextLocation())
{
//Start the walk animation
_robotAnimationState = _robotEnt.GetAnimationState("Walk");
_robotAnimationState.Loop = true;
_robotAnimationState.IsEnabled = true;
_isRobotWalking = true;
//Update the destination using the walklist.
LinkedListNode<Vector3> tmp; //temporary listNode
_nextWaypoint = _waypointList.First.Value; //get the next destination.
tmp = _waypointList.First; //save the node that held it
_waypointList.RemoveFirst(); //remove that node from the front of the list
_waypointList.AddLast(tmp); //add it to the back of the list.
//update the direction and the distance
_directionOfTravel = _nextWaypoint - _robotNode.Position;
_distanceToNextWaypoint = _directionOfTravel.Normalize();
}//if(nextLocation())
else //nowhere to go. set the idle animation. (or Die)
{
_robotAnimationState = _robotEnt.GetAnimationState("Idle");
//_robotAnimationState = _robotEnt.GetAnimationState("Die");
//_robotAnimationState.Loop = false;
}
}
else //we're in motion
{
//determine how far to move this frame
move = _robotWalkSpeed * _timeSinceLastFrame;
_distanceToNextWaypoint -= move;
}
if (_distanceToNextWaypoint <= 0.0f)
{
//set our node to the destination we've just reached & reset direction to 0
_robotNode.Position = _nextWaypoint;
_directionOfTravel = Vector3.Zero;
_isRobotWalking = false;
}//if(_distanceToNextWaypoint <= 0.0f)
else
{
//movement code goes here
_robotNode.Translate(_directionOfTravel * move);
//Rotation code goes here
Vector3 src = _robotNode.Orientation * Vector3.UnitX;
if ((1.0f + src.Dot(_directionOfTravel)) < 0.0001f)
{
_robotNode.Yaw(180.0f);
}
else
{
Quaternion quat = src.GetRotationTo(_directionOfTravel);
_robotNode.Rotate(quat);
}
}
}
}
}
Credits
Thanks Clay Culver for providing the original tutorial and to DigitalCyborg for the C#/ODN port.



