Intermediate Tutorial 5         Static Geometry
Print


warningHas not been ported to the Ogre Wiki Tutorial Framework
This tutorial is still pending an update to Ogre 1.7 and the Ogre Wiki Tutorial Framework.

Proceed at your own risk - strange things might happen.


Intermediate Tutorial 5: Static Geometry

Any problems you encounter while working with this tutorial should be posted to the Help Forum(external link).

Introduction

There will be a lot of situations when you need to add objects to your scene, but you won't need to move them at all. For example, unless you are adding some kind of physics into the mix, a rock or a tree will rarely ever need to move. For these situations, Ogre provides the StaticGeometry class, which allows you to build batches of objects to render in a big bunch. This is generally faster than doing it manually with SceneNodes. In this tutorial we will cover basic uses of StaticGeometry in your application. We will also cover a few more things you can do with ManualObject. See the previous tutorial for more information about ManualObject.

In this tutorial we will manually create a grass mesh, then add many of them to a StaticGeometry object in our scene.

You can find the code for this tutorial here. As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it. Note that this tutorial has been build largely from the Grass Demo that comes with Ogre's samples. You can also dig around in the source of that demo for more information.

Prerequisites

Create a cpp file in your IDE of choice and add the following code to it:

#include "ExampleApplication.h"
 
 class TutorialApplication : public -ExampleApplication
 {
 protected:
 public:
     TutorialApplication()
     {
     }
 
     ~TutorialApplication() 
     {
     }
 protected:
     MeshPtr mGrassMesh;
 
     void createGrassMesh()
     {
     }
 
     void createScene(void)
     {
         createGrassMesh();
         mSceneMgr->setAmbientLight(ColourValue(1, 1, 1));
 
         mCamera->setPosition(150, 50, 150);
         mCamera->lookAt(0, 0, 0);
 
         Entity *robot = mSceneMgr->createEntity("robot", "robot.mesh");
         mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(robot);
 
         Plane plane;
         plane.normal = Vector3::UNIT_Y;
         plane.d = 0;
         MeshManager::getSingleton().createPlane("floor",
             ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,
             450,450,10,10,true,1,50,50,Vector3::UNIT_Z);
         Entity* pPlaneEnt = mSceneMgr->createEntity("plane", "floor");
         pPlaneEnt->setMaterialName("Examples/GrassFloor");
         pPlaneEnt->setCastShadows(false);
         mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(pPlaneEnt);
     }
 };
 
 #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
 #define WIN32_LEAN_AND_MEAN
 #include "windows.h"
 
 INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
 #else
 int main(int argc, char **argv)
 #endif
 {
     // Create application object
     TutorialApplication app;
 
     try {
         app.go();
     } catch(Exception& e) {
 #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 
         MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
 #else
         fprintf(stderr, "An exception has occurred: %s\n",
             e.getFullDescription().c_str());
 #endif
     }
 
     return 0;
 }

Be sure you can compile and run the application before continuing. You should see a Robot standing on a flat plane.

Creating the Scene

Creating the Mesh

The first thing we need to do is create the grass mesh we will be rendering. The general idea is to create three square quads which overlap each other. Each quad will have a grass texture on it, so that when you look at it from any direction other than straight down it will look somewhat like 3D grass. The easiest way to overlap these quads will be to create one, then create another at a 60 degree rotation, and then a third at yet another 60 degrees.

As with the previous tutorial, we will be using ManualObject to generate our object, but unlike the previous tutorial we will be actually creating a mesh instead of simple line lists (which will require us to build an index buffer too, specifying the triangles as we build it).

The first thing we will do is define some basic variables. Since we are going to be rotating a quad that we are defining, it will be easiest if we use Ogre constructs like Vector3 and Quaternion to do the math for us instead of trying to do it manually. Our plan is to create a Vector3 with the X and Z coordinates in it, build a single quad from it, then rotate it with a Quaternion and repeat. Find the createGrassMesh member function and add the following code:

const float width = 25;
        const float height = 30;
        ManualObject mo("GrassObject");
 
        Vector3 vec(width/2, 0, 0);
        Quaternion rot;
        rot.FromAngleAxis(Degree(60), Vector3::UNIT_Y);

Now that we have set up our variables, we need to start defining the ManualObject. Unlike the previous tutorial, we will actually be designating a material to use with this object. Our RenderOperation will be set to be a triangle list as well, meaning after we define our vertices we must define a list of triangles which create the faces of our quads:

mo.begin("Examples/GrassBlades", RenderOperation::OT_TRIANGLE_LIST);
        for (int i = 0; i < 3; ++i)
        {

For each quad we are going to define four vertices, one for each corner. For each vertex we are also going to specify a texture coordinate. Texture coordinates tell Ogre how to sample the texture we have specified in the Examples/GrassBlades material. We will make the top left corner the (0, 0) point of the texture, the bottom right corner (1, 1) and so on:

mo.position(-vec.x, height, -vec.z);
            mo.textureCoord(0, 0);
 
            mo.position(vec.x, height, vec.z);
            mo.textureCoord(1, 0);
 
            mo.position(-vec.x, 0, -vec.z);
            mo.textureCoord(0, 1);
 
            mo.position(vec.x, 0, vec.z);
            mo.textureCoord(1, 1);

Now that we have defined the four corners of our quad, we now need to create faces. As we mentioned briefly in the previous tutorial, you must specify faces by creating triangles, and you must be sure to wind them counter clockwise to face towards you. For each quad, we will build two triangles. The first will be from the (0th, 3rd, 1st) vertices defined, and the second from the (0th, 2nd, 3rd) vertices defined. This properly defines the quad. Also remember that we are looping a few times, and every time through we create 4 vertices, thus we have to use an offset variable to select the proper vertex:

int offset = i * 4;
            mo.triangle(offset, offset+3, offset+1);
            mo.triangle(offset, offset+2, offset+3);

Next we need to rotate the vector we are using to create the current quad and continue looping. After the loop is finished we must call ManualObject::end to complete the object:

vec = rot * vec;
        }
        mo.end();

Now that we have defined the manual object, we are almost ready to start creating our StaticGeometry. One last thing we are going to do is create a mesh out of our ManualObject. Meshes are a touch more optimized than using a ManualObject directly for rendering. To do this, we simply need to call the ManualObject::convertToMesh with a name to store the mesh as:

mo.convertToMesh("GrassBladesMesh");

We are now finished creating the grass mesh. Note that if you have created a highly complex mesh in this way, you may save it to file and simply load the file back in instead of recreating the ManualObject every time you load the program. To do this, we will take the return value of convertToMesh (which we discarded in the actual code), and feed the Mesh it returns to the MeshSerializer::exportMesh function. Here is an example of how to do that:

// Do not add to the code!
        MeshPtr ptr = mo.convertToMesh("GrassBladesMesh");
        MeshSerializer ser;
        ser.exportMesh(ptr.getPointer(), "grass.mesh");

We are now ready to create the StaticGeometry.

Adding Static Geometry

The createScene method is already populated with several things. I have already added code to create a floor plane, add a robot, set the Camera's position, and so on since we have covered how to do those things in previous tutorials. Be sure to make sure you understand the current contents of this function before continuing.

The first thing we are going to do now is create an Entity based off of the grass mesh we created earlier and create a StaticGeometry object. Note that we will only create one Entity to use with our StaticGeometry. Find the createScene method and add the following code to the end of it:

Entity *grass = mSceneMgr->createEntity("grass", "GrassBladesMesh");
        StaticGeometry *sg = mSceneMgr->createStaticGeometry("GrassArea");
 
        const int size = 375;
        const int amount = 20;

The size variable will define how large of an area we are covering with grass and the amount variable will define how many objects we will put in each row of our StaticGeometry.

The next thing we need to do is define the size and origin of the StaticGeometry. Once we build the object (by calling StaticGeometry::build), we can no longer change the origin or region the StaticGeometry defines. The origin is the top left corner of the region that the StaticGeometry defines. If you want to place the StaticGeometry around a point, you will need set the origin's x and z coordinates to be half of the region's size for x and z:

sg->setRegionDimensions(Vector3(size, size, size));
        sg->setOrigin(Vector3(-size/2, 0, -size/2));

This will center the object around the point (0, 0, 0). To center it around a point in 3D space, you would need to do something similar to this:

// Do not add to the project!
        sg->setOrigin(Vector3(-size/2, -size/2, -size/2) + Vector3(x, y, z));

Where x, y, z is the point in 3D space to center it around. Also note that we do define the vertical height of the object when setting the region. Be sure that the y component of setRegionDimensions is at least as large as the highest object in the StaticGeometry.

The next thing we need to do is add objects to the StaticGeometry. This next piece of code is somewhat complex because we are adding a whole grid of grass to the geometry, and giving a random shift in x, z position, a random rotation, and a random vertical scale to it. In reality, the most important thing to understand in this is the StaticGeometry::addEntity:

for (int x = -size/2; x < size/2; x += (size/amount))
            for (int z = -size/2; z < size/2; z += (size/amount))
            {
                Real r = size / (float)amount / 2;
                Vector3 pos(x + Math::RangeRandom(-r, r), 0, z + Math::RangeRandom(-r, r));
                Vector3 scale(1, Math::RangeRandom(0.9, 1.1), 1);
                Quaternion orientation;
                orientation.FromAngleAxis(Degree(Math::RangeRandom(0, 359)), Vector3::UNIT_Y);
 
                sg->addEntity(grass, pos, orientation, scale);
            }

The addEntity function takes in the Entity to use, the position of the object, the orientation of the object, and the scale of the object. When you are defining StaticGeometry you will either use the addEntity function or the addSceneNode function. The addSceneNode function walks the -SceneNode adding all Entities to the static geometry, using the position, orientation, and scale of the children SceneNodes instead of specifying them manually. Note that if you use the addSceneNode function, be sure to remove the node from its parent -SceneNode, since the addSceneNode function does not remove it for you. If you do not, Ogre will render both the StaticGeometry you created and the original -SceneNode which is not what you want.

Finally we need to build the StaticGeometry before it is displayed:

sg->build();

Compile and run your application, you should now see a robot standing in a small patch of grass.

Conclusion

Modifying the StaticGeometry Object

Once the StaticGeometry is created, you are not supposed to do too much with it, since that would mostly defeat the purpose. You can, however, do things like wave the grass with the wind. If you are interested in how to do this, take a look at the grass demo which comes with Ogre. The GrassListener::waveGrass function modifies the grass to perform a wave-like motion.

Advanced Object Batching

This is, of course, just the beginnings of object batching. You should use StaticGeometry any time you have objects that are grouped together and will not move. If you are trying to create something as intensive or as expansive as a forest or trying to add grass to a huge amount of terrain, you should take a look at one of the more advanced batching techniques, like the PagedGeometry Engine.

Proceed to Intermediate Tutorial 6 Projective Decals


Alias: Intermediate_Tutorial_5


Contributors to this page: jacmoe60863 points  and Spacegaier1859 points  .
Page last modified on Monday 28 of June, 2010 17:37:36 GMT by jacmoe60863 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.