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

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(external link) 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(external link) method :

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(external link) method:

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:

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(external link). Original name, isn't it ? Anyway, here is how to create one:

// 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(external link):

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:

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: UV coordinates (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:

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:

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(external link):

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:

 

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:

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:

// "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(external link):

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:

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:

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:

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:

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:

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(external link):

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(external link):

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(external link):

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(external link):

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(external link):

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:

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:

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.

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(external link) (again, DirectX only).

Step by step

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

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(external link):

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:

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

... or:

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(external link):

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(external link):

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(external link) (really ?):

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:

mBone->setManuallyControlled(true);

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

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:

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:

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(external link):

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(external link), which can be created using Animation::createNodeTrack(external link):

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

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

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

You can then provide all the transformations :

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(external link):

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(external link), but with Skinning(external link) :-)

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(external link).

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

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(external link) (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:

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

... and update the flag for each bone:

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(external link)) doesn't draw your letters in vector graphics (it would be too costly). Actually, it rasterizes(external link) 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:

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

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

mFont->setType(Ogre::FT_IMAGE);

... or with a .ttf file:

mFont->setType(Ogre::FT_TRUETYPE);

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

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(external link) method for each character :

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:

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:

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:

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:

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:

mOverlay->setZOrder(500);

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

Close
informationOverlay 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.

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:

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:

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:

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:

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).

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:

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:

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).

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:

mTextArea->setFontName(yourFontName);

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

mTextArea->setCharHeight(16);

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

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

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

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.

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

You can add a child element quite easily:

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


Contributors to this page: sercero1 points  , van4dium562 points  , spacegaier6073 points  , OgreWikiBot54 points  , lingfors98 points  , jacmoe180265 points  and Beauty14565 points  .
Page last modified on Monday 30 of May, 2016 02:27:46 UTC by sercero1 points .


The content on this page is licensed under the terms of the Creative Commons Attribution-ShareAlike License.
As an exception, any source code contributed within the content is released into the Public Domain.