Skip to main content
Manual Resource Loading         How to load your data without using Ogre's file format.

Introduction

Why would anyone want manual resource loading? Well, sometimes you have the choice: You're working on a project with your own engine and your own file format (which you're used to) but now you want to use OGRE because it's more efficient/portable..., but sometimes you don't (e.g. for model viewers, ...).

Anyway, this article will cover manual loading for a few base resources: meshes, materials, skeletons.

Geometry

Introduction

There are two main ways to create your geometry: Using the ManualObject interface (simpler), or doing everything by yourself (obviously harder).

  • Note 1: I won't cover ManualObject here. If you want more information about it, please visit this tutorial. Be aware that it fits better to small object creation (but also works for bigger ones).
  • Note 2: If you aren't familiar with concepts such as "vertex buffer", "index buffer" or "vertex declaration", you'd better read a bit more about this subject. This site is a good place to start (DirectX only, but the concepts are the same).
  • Note 3: I'll assume you know how to load your model file into a readable format (arrays, ect).

Step by step

So, let's get started !

The first thing we need to do is to create our base mesh. To do this, we'll use the MeshManager::createManual method :

Copy to clipboard
Ogre::Mesh* mMesh = Ogre::MeshManager::getSingleton().createManual(yourMeshName, "General");

Note: MeshManager::createManual with Ogre 1.6 now returns an Ogre::MeshPtr.

Then, you have two choices :

  • Using shared geometry.
  • ... or not.


I don't know all the pros and cons, but I know at least 2 of them:

  • Shared geometry is more efficient if you're using static geometry
  • Non-shared geometry is necessary if you're using a lot of bones. I'll cover this point later.


Anyway, the process is almost the same in both cases: Create a vertex declaration, create a vertex buffer and fill it with your vertices, create an index buffer and fill it with... indices, yes. The only difference is that you'll have to repeat the process for each submesh if you don't use shared geometry.

Now to the details:

Sub-mesh

Almost all models have sub-meshes (I don't even know if it is possible not to have any in OGRE). They can be considered as "model parts" (a car's weel, an enemy's weapon...).
Creation is really easy: We'll use the Mesh::createSubMesh method:

Copy to clipboard
Ogre::SubMesh* mSubMesh = mMesh->createSubMesh(yourSubMeshName);

Note : use MeshPtr.get() to retrieve the Mesh from the MeshPtr.

yourSubMeshName doesn't have to be unique. But you'll be stuck if you want to get a SubMesh by its name later. If you don't want to bother giving a name, you can use:

Copy to clipboard
Ogre::SubMesh* mSubMesh = mMesh->createSubMesh();

It will create a SubMesh with an "index" value to access it. "index" starts at 0 and is incremented each time you create a new SubMesh (even with the first method).

Vertex declaration

The vertex declaration is an essential part of geometry loading: It tells the graphics card what kind of information is available for each vertex (position, normal, texture coordinate, color, ...).
OGRE offers a simple interface to create this declaration: VertexDeclaration. Original name, isn't it ? Anyway, here is how to create one:

Copy to clipboard
// We first create a VertexData Ogre::VertexData* data = new Ogre::VertexData(); // Then, we link it to our Mesh/SubMesh : #ifdef SHARED_GEOMETRY mMesh->sharedVertexData = data; #else mSubMesh->useSharedVertices = false; // This value is 'true' by default mSubMesh->vertexData = data; #endif // We have to provide the number of verteices we'll put into this Mesh/SubMesh data->vertexCount = iVertexCount; // Then we can create our VertexDeclaration Ogre::VertexDeclaration* decl = data->vertexDeclaration;

We're then ready to give it some data! To do so, we'll use VertexDeclaration::addElement:

Copy to clipboard
decl->addElement(0, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);

Here we are: we told OGRE that our vertices will be composed of 3 float values, that OGRE must interpret as a position.
And what if we want more? If we want our Mesh to render properly, we'll have to provide information about its normals (these are used for lighting). Here is how:

Copy to clipboard
size_t offset = 0; decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION); offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3); decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);

As you may have noticed, we introduced a new variable: offset. This one is used to tell Ogre that normals will be located offset after the vertex adress.

Ok, now that you've understood the concept, let's add another element: {LEX(page="UV")}UV coordinates{LEX} (texture coordinates). Since there is a special syntax that must be used when adding multiple elements of the same semantics we will just to illustrate this add two sets of texture coordinates (see note 2, below). Easy:

Copy to clipboard
size_t offset = 0; decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION); offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3); decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3); decl->addElement(0, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES); offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2); decl->addElement(0, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 1);

Good! But there is a little thing you must be aware of if you want to animate your Mesh later: Software skinning (and maybe vertex animation) requires that positions and normals are contained in a separate buffer from the other elements.

Hu, and then? Well, in the latest vertex declaration we wrote, we told Ogre that everything was located in the same buffer. How do I know this? Look at the first argument of addElement: "0". It's the index of the vertex buffer in which OGRE will search for your element. Here, we told OGRE that everything was located in the first buffer. Let's change this:

Copy to clipboard
size_t offset = 0; decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION); offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3); decl->addElement(0, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); decl->addElement(1, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);

It's better. As you might have noticed, the offset is set back to 0 when you use a different buffer.

Note 1: Blend weights and indices will be covered later.

Note 2: If you want to create two elements with the same semantic (the same "role"), you must use the optional argument of addElement. It's an index you'd have to increase each time you create a new element that has already been used. If you omit the parameter, as we did for the first texture coordinate set, it defaults to 0. The second set we set to "1".

Vertex buffer(s)

Okay, now that we have a good vertex declaration, we are ready to play with vertex buffer(s)!

As I've told you in the previous section, we will create two vertex buffers: one for positions and normals and another for UV coordinates. Let's use HardwareBufferManager::createVertexBuffer:

Copy to clipboard
Ogre::HardwareVertexBufferSharedPtr vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( decl->getVertexSize(0), // This value is the size of a vertex in memory iVertexNbr, // The number of vertices you'll put into this buffer Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY // Properties );

Now, there are several ways to write data into your new buffer:



Copy to clipboard
vbuf->writeData(0, vbuf->getSizeInBytes(), array, true);

Anyway, you'll need to write your data in a particular order: the one you provided in the vertex declaration:

Copy to clipboard
array[0] = vertices[1].x array[1] = vertices[1].y array[2] = vertices[1].z array[3] = vertices[1].normal.x array[4] = vertices[1].normal.y array[5] = vertices[1].normal.z array[6] = vertices[2].x ...

Now that this buffer is ready, we still need to link it to our Mesh/SubMesh! Here is how:

Copy to clipboard
// "data" is the Ogre::VertexData* we created before Ogre::VertexBufferBinding* bind = data->vertexBufferBinding; bind->setBinding(0, vbuf);

Great! Now let's do the same for UV coordinates. Actually, there are only two differences: We need to change decl->getVertexSize(0) to decl->getVertexSize(1) in the createVertexBuffer method and bind->setBinding(0, vbuf); becomes bind->setBinding(1, vbuf);.

"0" was for the first buffer, "1" if for the second, "2" would be for the third, ...

Index buffer

Now for the last one: Ihe index buffer doesn't work the same way as vertex buffers do. We have several options:

  • If you're using shared geometry, then we can either:
    • create an index buffer per SubMesh...
    • ...or create a global index buffer.
  • Else if you're not using shared geometry, we'll have to create an index buffer per SubMesh.


Here is how to create one, using HardwareBufferManager::createIndexBuffer:

Copy to clipboard
Ogre::HardwareIndexBufferSharedPtr ibuf = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer( Ogre::HardwareIndexBuffer::IT_16BIT, // You can use several different value types here iIndexNbr, // The number of indices you'll put in that buffer Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY // Properties );

Before telling you how to write some data, we'll see how OGRE links index buffers to their SubMeshes:

Copy to clipboard
mSubMesh->indexData->indexBuffer = ibuf; // The pointer to the index buffer mSubMesh->indexData->indexCount = iIndexNbr; // The number of indices we'll use mSubMesh->indexData->indexStart = 0; // The offset from the beginning of the index buffer

Why do we need an offset? If you're using a global index buffer, then you'll mix up several SubMeshes in the same buffer:

Copy to clipboard
index[0] = subMesh[0].vertexIndices[0] index[1] = subMesh[0].vertexIndices[1] index[2] = subMesh[0].vertexIndices[2] index[3] = subMesh[1].vertexIndices[0] index[4] = subMesh[1].vertexIndices[1] index[5] = subMesh[1].vertexIndices[2] ...

subMesh[0] will have an offset of "0", but subMesh[1] will have "3". Both of them will have an index count of "3".

If you aren't using a global index buffer, then the offset will always be "0".

At last, we'll fill it with some data, using the same method you chose for vertex buffers.

Now that all our geometry is loaded, we still need to do a few things before we can use our Mesh:

Bounding box and sphere

I'm not sure, but I think those are necessary for Ogre to perform an accurate clipping (don't render objects that are not visible on the screen).

If you're using a static geometry, then this step is really easy: when you read your vertices from your file, only keep the biggest and lowest values on each axis (xMax, xMin, yMax, yMin, zMax, zMin). Then:

Copy to clipboard
mMesh->_setBounds(Ogre::AxisAlignedBox(xMin,yMin,zMin,xMax,yMax,zMax)); mMesh->_setBoundingSphereRadius(std::max(xMax-xMin, std::max(yMax-yMin, zMax-zMin))/2.0f);

Anyway, the best option is to include this information directly on your model file.

One last step

Call:

Copy to clipboard
mMesh->load();

You're done ๐Ÿ˜Š

Conclusion

Here is a little diagram that shows you the great lines of manual geometry loading:

Shared geometry:

  • Create your Mesh object
  • Create the vertex declaration
  • Create your different vertex buffers (separate [position+normals] from the rest if you're animating your mesh)
  • Link them to your Mesh
  • Create your index buffer
  • Create your SubMeshes and link them to the index buffer
  • Call Mesh::load()


Non-shared geometry:

  • Create your Mesh object
  • For each SubMesh:
    • Create the vertex declaration
    • Create your different vertex buffers (separate [position+normals] from the rest if you're animating your mesh)
    • Link them to your SubMesh
    • Create your index buffer
    • Link it to your SubMesh
  • Call Mesh::load()

Material

Introduction

I'm still learning on this subject. I know the very basics, but I'll edit this part whenever I know something useful.

Step by step

Creating a Material starts the same way as creating a Mesh:

Copy to clipboard
Ogre::MaterialPtr mMat = Ogre::MaterialManager::getSingleton().create(yourMaterialName, "General", true);

Then, everything is pretty straightforward:

Technique

The Material we just created starts with only one Technique.

If you ever need more, you'll need to create one using Material::createTechnique:

Copy to clipboard
Ogre::Technique* mTech = mMat->createTechnique();

Pass

Each Technique we create (including the one automatically provided with the Material) starts with one Pass.

If you need more than one, you can use Techique::createPass:

Copy to clipboard
Ogre::Pass* mPass = mTech->createPass();

TextureUnitState

We'll have to create one TextureUnitState each time we want to use a texture. Pass objects don't automatically come with one created, so here is how we'll do this, using Pass::createTextureUnitState:

Copy to clipboard
Ogre::TextureUnitState* mTexUnitState = mPass->createTextureUnitState();

Adding a bit of color

Now that we know how to create the different parts of a Material, we'll see how to add something useful: diffuse color.

This is a property of the Pass object, so we'll use Pass::setDiffuse:

Copy to clipboard
mPass = mMat->getTechnique(0)->getPass(0); mPass->setDiffuse(Ogre::ColourValue(red, green, blue, alpha));

How about a texture?

This part is a bit harder, we first need to create a Texture using TextureManager::load:

Copy to clipboard
Ogre::TexturePtr mTex = Ogre::TextureManager::getSingleton().load(yourTextureFileName, "General");

Then, we need to create a TextureUnitState for our Pass and link the texture to it:

Copy to clipboard
mPass->createTextureUnitState()->setTextureName(mTex->getName());

Usage

Now, we're very happy: We have a Material with a beautiful texture. But, how do we use it actually?

Materials are assigned to SubMeshes. Assignation is really easy:

Copy to clipboard
mSubMesh->setMaterialName(yourMaterialName);

... and that's all! You can also use the name of a Material you loaded in a .material file.

Good to know

If you want to use your Material for a 2D component (an overlay, a sprite, whatever), you'll need to make a few adjustments for it to render properly on the screen.

Copy to clipboard
mPass->setCullingMode(Ogre::CULL_NONE); // No culling (triangles can be seen from both sides) mPass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); // Allow transparency from alpha channel (tga, png, ...) mMat->setLightingEnabled(false); // Disable lighting mMat->setDepthCheckEnabled(false); // No depth check

(You may only use a few of these depending on the case)

Questions

Is the load() method necessary ? When should it be called ?

Skeleton

Introduction

Here comes the hardest part...

As for geometry loading, you'll need a few prerequisites before anything else: I'd recommend reading this paper (again, DirectX only).

Step by step

Like every resource we created from now, a Skeleton needs to be created from the appropriate manager:

Copy to clipboard
Ogre::Skeleton* mSkel = Ogre::SkeletonManager::getSingleton().create(yourSkeletonName, "General", true);

Now, depending on the way you want to play with your skeleton, you can decide to use OGRE's animation system or to update your bones yourself (for ragdolls or if you want more flexibility).

Bone

Anyway, once our Skeleton is created, we'll need to create a few bones with Skeleton::createBone:

Copy to clipboard
Ogre::Bone* mBone = mSkel->createBone();

Yet, if you create all your bones with this method, they will all be root bones (they have no parent).

To create a parent-child relation, you have two ways:

Copy to clipboard
Ogre::Bone* mParent; // You have created this one before Ogre::Bone* mBone = mSkel->createBone(); mParent->addChild(mBone);

... or:

Copy to clipboard
Ogre::Bone* mParent; // You have created this one before Ogre::Bone* mBone = mParent->createChild();

There is absolutely no difference between the two. The result is the same. But sometimes you'll need to create all your bones first and then link them together. You'd better use the first method in that case.

Position: Each bone has an initial position which defines the default pose.
Note that Ogre::Bone is derived from Ogre::Node, so its position is relative to its parent.
To set a bone's position, you can use Bone::setPosition:

Copy to clipboard
Ogre::Vector3 mPos(0.0f, 0.0f, 0.0f); mBone->setPosition(mPos);

Orientation: Most of the time, animations are made of rotations.
Here, you can provide this bone a base orientation using Bone::setOrientation:

Copy to clipboard
Ogre::Quaternion mRot(1.0f, 0.0f, 0.0f, 0.0f); // Be careful : Ogre's quaternion are (w, x, y, z) mBone->setOrientation(mRot);

Scale: Just like the two above, using Bone::setScale (really ?):

Copy to clipboard
Ogre::Vector3 mScale(1.0f, 1.0f, 1.0f); mBone->setScale(mScale);

A few details: Now, if you're planning to manage your bones yourself, we'll need to set a very important parameter:

Copy to clipboard
mBone->setManuallyControlled(true);

... and, depending on the way you want to handle your animations, you can call:

Copy to clipboard
mBone->setInitialState();

This will tell OGRE that the actual Bone's parameter (position, rotation, scale) will be kept as a base for all future transformations.
If you aren't using Ogre's animation system, then you can retrieve this initial state by calling:

Copy to clipboard
mBone->resetToInitialState();

If not, Ogre does it automatically.

Animation

If you don't want to use OGRE's animation system, then you don't need to read this part. I'll assume you know what you're doing, and that you know how to handle your own data.

I'll just show you how to apply your transformations to your Bone:

Copy to clipboard
mBone->setScale(scale); mBone->setOrientation(rotation); mBone->translate(mLastTrans*(-1)); mBone->translate(translation);

Line number 3 is required if your translation is relative to the Bone's initial position (mLastTrans is the translation you applied to your bone on the previous update).

Else, we'll do a bit of abstract:

Let's say you have 2 different animations for you model ("stand" and "walk"). Ogre will require that you create an animation track for each bone and for each animation.

So, basically you'll have this:

  • Bone[0] :
    • Create animation track for "stand"
    • Create animation track for "walk"
  • Bone[1] :
    • ...


I don't know for you, but I'm used to having only one "track" (or "timeline") per bone, so I found it to be a little confusing at first.

Anyway, let's create all these!

Animation creation: Before loading your animation data, you'll surely want to create your different animations.
To do that, we'll use Skeleton::createAnimation:

Copy to clipboard
Ogre::Animation* mAnim = mSkel->createAnimation(yourAnimationName_uniqueForThisModel, yourAnimationLength_inSeconds);

Simple.

Animation tracks: Okay, now back to our Bones!
For each animation, you'll need to create an animation track. It's a container which associates a key (the time) and a value (the transformation). There are several types of animation tracks, but for skeletal animation, we only need one: the NodeAnimationTrack, which can be created using Animation::createNodeTrack:

Copy to clipboard
Ogre::NodeAnimationTrack* mTrack = mAnim->createNodeTrack(yourTrackHandle_uniqueForThisAnimation, mBone);

Now that it is created, we can fill it with some TransformKeyFrames that we'll have previously created using NodeAnimationTrack::createNodeKeyFrame:

Copy to clipboard
Ogre::TransformKeyFrame* mKey = mTrack->createNodeKeyFrame(time);

You can then provide all the transformations :

Copy to clipboard
Ogre::Vector3 translate(0.0f,0.0f,0.0f); mKey->setTranslate(translate); Ogre::Vector3 scale(1.0f,1.0f,1.0f); mKey->setScale(scale); Ogre::Quaternion rotation(1.0f,0.0f,0.0f,0.0f); mKey->setRotation(rotation);

Note: If you don't provide a transformation, Ogre will use the identity matrix (either a zero translation, a 1x scale or a zero rotation).
Example:

  • Key[0]
    • Translation = 0, 0, 5
    • Scale = 1, 1, 0.5
    • Rotation = 1, 0, 0, 0
  • Key[1]
    • Translation = 0, 0, 15
    • Rotation = 1, 0, 0, 0

... here, you didn't provide the scale for the second key. If you're assuming OGRE will be using the one from the previous key, then you're wrong!
Key[1] will actually look like this:

  • Key[1]
    • Translation = 0, 0, 15
    • Scale = 1, 1, 1 <--- the scale identity, default parameter
    • Rotation = 1, 0, 0, 0


Ending: Okay, now we've got a Skeleton, with several Animations that we have linked to Bones using NodeAnimationTracks. Good!
We have a last thing to do before everything is done: link the Skeleton to the Mesh.
We'll be using Mesh::_notifySkeleton:

Copy to clipboard
mMesh->_notifySkeleton(mSkel);

And that's all.

Now, if you're used to all that skeleton and bone stuff, you must be thinking: We still miss something.

And you're right: We didn't link our skeleton to our vertices! (No, _notifySkeleton doesn't do it automatically...). We'll see how in the next chapter.

Skinning

Note: This section will not deal with Skinning, but with Skinning ๐Ÿ˜Š

In the 3D jargon, skinning is the action of linking a skeleton to a 3D mesh and its vertices.

Depending on your model format, it may be a difficult task, or end up as simple as creating a new Material. Why that?

Skeletal animation is an heavy process. You can do it in software (calculation done by the CPU) or in hardware (calculation done by the GPU). The first way is the most simple to implement, with very few limitations, but is also the slowest method. The last way is much faster, but you have to communicate with your graphics card (with shaders) and this fact imposes you a few limitations.

DirectX's vertex shader version 2.0 and 3.0 allow 256 float4 constants, which translates to 64 matrices per vertex (256 is the minimum allowed by 2.0 and 3.0, it can vary depending on the graphics card).

If you've never played with shaders, you may be thinking "64 matrices for a single vertex is way too much!". That's true, a vertex uses at most 5 matrices (4 for skeletal animations, 1 for world/view/projection). That said, you should never try to send 5 matrices per vertex to your graphics card. Sending data to the graphics card is costly. That's why you're told everywhere to batch your data as much as you can.

The method that is chosen most of the time is to split the Skeleton up into a local bone list for each SubMesh. Actually, a SubMesh doesn't use the whole Skeleton (example: My models never use more than 60 matrices per SubMesh, and that's still a high number).

So, if your model format already contains a local bone list and all the information needed, you'll be fine. But else, you'll be obliged to build this list at runtime.

Actually, I haven't tested hardware skinning yet. So I don't know how OGRE handles all that. I'll edit this part when I know more, but if you already know, go on and edit this article.

Anyway, the bones and their weights will be stored in a vertex buffer, just like position, normals or UV coordinates. Ogre offers a cool way to do it without manually creating the buffers: VertexBoneAssignment.

We'd better create it just after our SubMesh's vertex buffers. VertexBoneAssignement is done per vertex:

Copy to clipboard
for (int i = 0; i < iVertexNbr; i++) { Ogre::VertexBoneAssignment vba; // The index of the vertex in the vertex buffer vba.vertexIndex = static_cast<unsigned int>(i); for (int j = 0; j < MAX_BONE_PER_VERTEX ; j++) { // The index of the bone we want to use vba.boneIndex = bones_ind[i][j]; // The weight we want to give to that bone (the sum of all weights for a single vertex must be 1.0f) vba.weight = bones_wgt[i][j]; mSubMesh->addBoneAssignment(vba); } } // Apply changes, build the buffer mSubMesh->_compileBoneAssignments();

The problem here, is that we're using global bone indices: They dirrectly refer to bones in the skeleton. So if you ever use hardware skinning, you'll be in trouble if you have a lot of bones.

I know that it has something to do with Model::blendIndexToBoneIndexMap (public member), but I'm not sure how to use it. Judging from the description given in the docs, it seems that Ogre creates it on its own when you compile your VertexBoneAssignements. Maybe there nothing else to do...

Side note

Now that everything is ready, there is a little thing to have in mind if you want to animate your bones yourself.

When you create an Entity with your Mesh, OGRE forgets the setManuallyControlled flag you set before! You'll need to re-set it to true for each bones, else your Entity won't be animated.

To do so, you'll need to get the SkeletonInstance created for your Entity:

Copy to clipboard
Ogre::SkeletonInstance* mSkel = mEntity->getSkeleton();

... and update the flag for each bone:

Copy to clipboard
for (int i = 0; i < iBoneNbr; i++) { mSkel->getBone(i)->setManuallyControlled(true); }

You should then be ready to play with your animated model!

Font

Introduction

The Font object is used to display some text on the screen, and holds data about the shape of your letters.

There are two font types:
Image font: It relies on a texture file (a .jpg, .png or whatever) on which each letter is drawn one after another.

  • Pros: very fast, allows (almost) unlimited letter shape (including outlines, etc).
  • Cons: gets blurry when scaled (up or down).

TrueType font (OpenType works too): These are based upon a font file (.ttf), which contains vector graphics.
One thing to note though : the library that OGRE uses (FreeType) doesn't draw your letters in vector graphics (it would be too costly). Actually, it rasterizes the vector graphics into a texture, which is eventually used just like an Image font.

  • Pros: allows the use of unlimited sizes while keeping the same quality (using only one file).
  • Cons: it suffers from the same limitations as all TrueType fonts (only one color).

Note: you can't change the size of the font once it is created.

Actually, the font type just defines the way to load it into OGRE. You'll end up eventually with the same thing: a texture containing all the letters (or the ones you chose).

Both of those loading methods will be covered.

Step by step

As everything in this tutorial, we create a Font object using the FontManager:

Copy to clipboard
Ogre::FontPtr mFont = Ogre::FontManager::getSingleton().create(yourFontName_unique, "General");

Then comes the choice to load your Font with a texture:

Copy to clipboard
mFont->setType(Ogre::FT_IMAGE);

... or with a .ttf file:

Copy to clipboard
mFont->setType(Ogre::FT_TRUETYPE);

No matter what you have chosen, we'll then have to tell OGRE what file to load:

Copy to clipboard
mFont->setSource(source)

If you have chosen an Image font, then source will be the path to your texture. Else, if you have chosen a TrueType font, then source will be the path to your .ttf file.

Then, we'll have to split the tutorial into two parts because the loading process is completely different:

Image font

Now, OGRE has a texture with plenty of things on it. How does it know where to find the 'A' character ?
It doesn't ๐Ÿ˜Š Well, not until you've told him where every single character is located...
To do so, you'll have to call the Font::setGlyphTexCoords method for each character :

Copy to clipboard
Ogre::Font::setGlyphTexCoords(id, u1, v1, u2, v2, textureAspect);
  • id is the character code (33, 255, ...) that you want to add.
  • u1 and v1 are the coordinates of the top-left corner of the corresponding letter
  • u2 and v2 are the coordinates of the bottom-left corner.
  • textureAspect... well I don't know : textureAspect is the width/height of the texture (may be non-square) (quote from the docs). Why does it have to be set for each character if this a value is always the same ?


Anyway, your Font object is now ready to be used.
Note: I've never tried this out, so I may be wrong. If somebody ever has a texture font ready, please confirm that nothing else is needed.

TrueType font

The first thing we will define is the quality of the produced texture.
To do that, OGRE provides two functions:

Copy to clipboard
mFont->setTrueTypeSize(size); mFont->setTrueTypeResolution(resolution);

At that time, I don't know exactly how to use them. But I can tell you this:

  • size should be the font size you're planning to use with that Font object (example : 16pts).
  • resolution defines the quality of your font (dot per inches...). I've read somewhere that a value of 96 is good for basic use (no matter how big you'll render your text).


Then, the last thing we'll do is to select what character we'll use:

Copy to clipboard
mFont->addCodePointRange(Ogre::Font::CodePointRange(33, 255));

This tells OGRE that we'll use characters from number 33 to number 255 (this includes accentuated letters such as รƒยฉ, รƒยฟ, รƒยฑ, รขโ‚ฌโ„ข, ..., all the common punctuation marks, signs and numbers).
If you want to use the whole [33, 255] range, but you do not want the character 125, then you'll have to call it twice:

Copy to clipboard
mFont->addCodePointRange(Ogre::Font::CodePointRange(33, 124)); mFont->addCodePointRange(Ogre::Font::CodePointRange(126, 255));

Note: OGRE automatically uses the [33, 166] range if none is provided.

That's all!

Overlay

Introduction

Overlay creation is so easy that I've merged it and OverlayElement into a single section.

This subject is already covered there: (click me), but I want to merge everything into a single place. Basically, what you'll find in this section will be exactly the same thing as what's available in the other article. The only difference is that I'll document a bit more each step.

Step by step

Overlay

As I've said earlier, Overlay creation is really easy. If you've read the whole article until now, then the following will not surprise you:

Copy to clipboard
Ogre::OverlayManager* mOverlayMgr = Ogre::OverlayManager::getSingletonPtr(); Ogre::Overlay* mOverlay = mOverlayMgr->create(yourOverlayName_unique);

Then, as for every GUI element, an Overlay has show() and hide() methods. You should note that every Overlay you just created (or loaded from an .overlay file) is hidden by default. So, if you want its content to appear on the screen, you'll have to call show() once.

But, only do this when you've loaded your whole GUI hierarchy ! show() will automatically call initialize() on the overlay and its children (if not already initialized).

The only other thing you might ever need to do with your Overlay is to change it's Z value. We'll do this right now:

Copy to clipboard
mOverlay->setZOrder(500);

Note: The Z value is capped to 650 (default is 100).

 Overlay System as a Component

If you are using a version of Ogre where the Overlay System has been extracted into a separate component (Ogre 1.9+), the following steps are needed to get it up and running:

  1. Create the Root object.
  2. Create an OverlaySystem object.
  3. Initialize the Root object.

You also have to register the OverlaySystem object as a RenderQueueListener in your scene manager.

Copy to clipboard
Ogre::Root * root = new Ogre::Root; Ogre::OverlaySystem * overlaySystem = new Ogre::OverlaySystem; root->initialise(...); Ogre::SceneManager * sceneManager = root->createSceneManager(...); sceneManager->addRenderQueueListener( overlaySystem );

OverlayElement

An Overlay is not a graphical part of the GUI. It's just a base container in which we'll put some OverlayElements.

To create an OverlayElement, we must use the OverlayManager once again:

Copy to clipboard
Ogre::OverlayElement* mElement = mOverlayMgr->createOverlayElement(yourElementType, yourElementName_unique);

It has several methods to position and scale it on the screen, but OGRE can interpret our calls in two different ways:

Before calling anything, we must define which of these ways we want:

Copy to clipboard
mElement->setMetricsMode(mode);

mode can be either:

  • Ogre::GMM_PIXELS (absolute coordinates)
  • Ogre::GMM_SCREEN (screen relative coordinates)


In addition, we also have some methods to tell OGRE which point we're considering as the parent's position. By default, it's the top-left corner of our element's parent (only works with Ogre::GMM_PIXELS ?). But we'll change this:

Copy to clipboard
mElement->setVerticalAlignment(Ogre::GVA_BOTTOM); mElement->setHorizontalAlignment(Ogre::GHA_RIGHT);

Now our element's anchor point is located in its parent's bottom-right corner.

So, the other two methods we'll need are:

Copy to clipboard
mElement->setPosition(left, top);

Note: If mode is Ogre::GMM_SCREEN, then setPosition(1, 1) will put our element on the bottom-right corner of the screen. Else, it will offset our element from the top-left corner of the screen (in pixels).

Copy to clipboard
mElement->setDimensions(width, height);

Note: If mode is Ogre::GMM_SCREEN, then setDimension(1, 1) will make our element the same size as the screen. Else, it'll simply set the number of pixel of our element's width and height.

Then, the most interesting:

Copy to clipboard
mElement->setMaterialName(yourMaterialName);

You can assign a material to your element! That allows you to display a texture, or a plain color rectangle. Cool.

Once our element is created, we should link it to the Overlay:

Copy to clipboard
mOverlay->add2D(mElement);

Ok that's all.

Now, you may think: "Hum, it's pretty basic". That's true!

But OverlayElement is actually a virtual class from which two other ones inherits:

  • TextAreaOverlayElement
  • OverlayContainer


(Note: You can make your own too!)

They also have their own methods, and I'll describe them here :

TextAreaOverlayElement: This one is used to display some text on the screen (using a Font object).

Copy to clipboard
Ogre::TextAreaOverlayElement* mTextArea = static_cast<Ogre::TextAreaOverlayElement*>( mOverlayMgr->createOverlayElement("TextArea", yourElementName_unique) );

The first thing we want to do is to set which Font we'll be using:

Copy to clipboard
mTextArea->setFontName(yourFontName);

... then, the size at which we want our text to be drawn:

Copy to clipboard
mTextArea->setCharHeight(16);

... we can also play with the color of the text:

Copy to clipboard
mTextArea->setColour(Ogre::ColourValue(0.3, 0.5, 0.3));

... and eventually set a caption (the text to render):

Copy to clipboard
mTextArea->setCaption("Hello OGRE!");

OverlayContainer: The particularity of this element is that it can contain other elements!
Useful when you want to group your element in a hierarchy.

Copy to clipboard
Ogre::OverlayContainer* mPanel = static_cast<Ogre::OverlayContainer*>( mOverlayMgr->createOverlayElement("Panel", yourElementName_unique) );

You can add a child element quite easily:

Copy to clipboard
mPanel->addChild(mTextArea);

Then, if you move mPanel, mTextArea will follow.

Unloading ?

I don't know yet what I should do to ensure a proper unloading. If you have some advices, please share !

Alias: Manual_Resource_Loading