Tutorial Intermedio 5: Geometría Estática


external image help.gifCualquier problema que encuentres mientras usas el tutorial deberías preguntarlo en los Foros de ayuda.

Introducción


Habra muchas situaciones cuando necesites añadir objetos a tu escena, pero no necesites moverlos. Por ejemplo, a menos que estes añadiendo alguna clase de física dentro de la mezcla, una roca o un árbol raramente necesitaran moverse.
Para estas situaciones, Ogre proporciona la clase StaticGeometry, que te permite construir procesos por lotes de objetos para renderizarlos en un gran bloque. Esto es generalmente más rapido que hacerlo manualmente mediante los SceneNodes. En este tutorial cubriremos unas pocas cosas que puedes hacer con ManualObject. Mirar el tutorial anterior para más información sobre ManualObject.

En este tutorial crearemos manualmente una malla de hierba, entonces añadiremos muchas de ellas a un objeto de StaticGeometry en nuestra escena.

Puedes encontrar el código para este tutorial aquí. Deberías ir añadiendo código a tu proyecto poco a poco y mirar los resultados.

Prerrequisitos


Carga un nuevo proyecto con el Tutorial Framework Ogre y ajusta tu proyecto a esto:

IntermediateTutorial5.h
#ifndef IntermediateTutorial5_h_
#define IntermediateTutorial5_h_
 
#include "BaseApplication.h"
 
class IntermediateTutorial5 : public BaseApplication
{
public:
 IntermediateTutorial5(void);
 virtual ~IntermediateTutorial5(void);
 
protected:
 virtual void createScene(void);
 virtual void createGrassMesh(void);
};
 
#endif #ifndef IntermediateTutorial5_h_
IntermediateTutorial5.cpp
*/
#include "IntermediateTutorial5.h"
 
// -------------------------------------------------------------------------------------
IntermediateTutorial5::IntermediateTutorial5(void)
{
}
// -------------------------------------------------------------------------------------
IntermediateTutorial5::~IntermediateTutorial5(void)
{
}
 
void IntermediateTutorial5::createGrassMesh(void)
{
}
// -------------------------------------------------------------------------------------
void IntermediateTutorial5::createScene(void)
{
 createGrassMesh();
 mSceneMgr->setAmbientLight(Ogre::ColourValue::White);
 
 mCamera->setPosition(150, 50, 150);
 mCamera->lookAt(0, 0, 0);
 
 Ogre::Entity* robot = mSceneMgr->createEntity("robot", "robot.mesh");
 mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(robot);
 
 Ogre::Plane plane;
 plane.normal = Ogre::Vector3::UNIT_Y;
 plane.d = 0;
 
 Ogre::MeshManager::getSingleton().createPlane("floor", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 450.0f, 450.0f, 10, 10, true, 1, 50.0f, 50.0f, Ogre::Vector3::UNIT_Z);
 Ogre::Entity* planeEnt = mSceneMgr->createEntity("plane", "floor");
 planeEnt->setMaterialName("Examples/GrassFloor");
 planeEnt->setCastShadows(false);
 mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(planeEnt);
}
 
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
 
#ifdef cplusplus
extern "C" {
#endif
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
 INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
 int main(int argc, char *argv[])
#endif
 {
 // Crea un objeto de aplicacion
 IntermediateTutorial5 app;
 
 try {
 app.go();
 } catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
 MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
 std::cerr << "An exception has occured: " <<
 e.getFullDescription().c_str() << std::endl;
#endif
 }
 
 return 0;
}
 
#ifdef __cplusplus
}
#endif
Asegúrate de que has compilado y ejectuado la aplicación antes de continuar. Deberías ver un Robot en un suelo plano.

Creando la Escena

Creando la Malla


La primera cosa que necesitamos hacer es crear la malla de hierba que renderizaremos. La idea general es crear tres quads que se solapen. Cada quad tendrá una textura de hierba sobre él, asi que cuando le mires desde una dirección verás que parece como hierba 3D. El modo más sencillo de solapar estos quads sera crear uno, entonces otro con rotación de 60 grados, y el tercero con otros 60 grados más.

Como con el tutorial anterior, usaremos el ManualObject para generar nuestro objeto, pero a diferencia del tutorial anterior crearemos una malla en vez de una simple list de líneas (necesitará de nosotros para construir un búfer índice, especificando los triángulos lo construimos).

La primera cosa que vamos a hacer es definir algunas variables básicas. Ya que estamos rotando un quad que estamos definiendo, será lo más fácil si usamos constructores de Ogre como Vector3 y Quaternion para hacer la matemática por nosotros en vez de intentar hacerla manualmente. Nuestro plan es crear un Vector3 con las coordenadas X y Z en el, construyendo un único quad desde él, entonces rotarlo con un Quaternion y repetirlo. Busca la función miembro createGrassMesh y añade el siguiente código:
const float width = 25;
const float height = 30;
Ogre::ManualObject mo("GrassObject");
 
Ogre::Vector3 vec(width/2, 0, 0);
Ogre::Quaternion rot;
rot.FromAngleAxis(Ogre::Degree(60), Ogre::Vector3::UNIT_Y);
Ahora que hemos establecido nuestras variables, necesitamos comenzar definiendo el ManualObject. A diferencia del tutorial anterior, estamos diseñando el material para usarlo con este objeto. Nuestro RenderOperation establecerá una lista de triángulos tan bien, lo que significa que después de que definamos nuestros vértices debemos definir una lista de triángulos que creará las caras de nuestros quads:
mo.begin("Examples/GrassBlades", Ogre::RenderOperation::OT_TRIANGLE_LIST);
for (int i = 0; i < 3; ++i)
{
Para cada quad vamos a definir cuatro vértices, uno para cada esquina. Para cada vértice vamos a especificar también una coordenada de textura. Las coordenadas de textura le dicen a Ogre como ha sido especificado el ejemplo de textura en el material Examples/GrassBlades. Haremos que la esquina superior izquierda sea (0,0) de la textura, y la esquina abajo derecha sea (1,1):
 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);
Ahora que hemos definidos las cuatro esquinas de nuestro quad, necesitamos crear las caras. Como mencionamos brevemente en el tutorial anterior, debemos especificar las caras creando los triángulos, y debes asegurarte de orientarlos a favor de las agujas del reloj para que la cara apunte hacia ti. Para cada quad, construiremos dos triángulos. El primero estará desde los vértices definidos (0,3,1) y el segundo desde (0,2,3). Esto definirá apropiadamente el quad. También recuerdo que estamos moviéndonos en bucle varias veces, y cada vez creamos cuatro vértices, además tenemos que usar una variable de desplazamiento para seleccionar el vértice apropiado:
int offset = i * 4;
 mo.triangle(offset, offset+3, offset+1);
 mo.triangle(offset, offset+2, offset+3);
a continuación rotamos el vector que estamos usando para crear el quad actual y continuamos en el bucle. Después de que el bucle haya finalizado llamamos a ManualObject::end para completar el objeto:
vec = rot * vec;
}
mo.end();
Ahora que hemos definido el objeto manual, estamos listos para empezar a crear nuestro StaticGeometry. Una última cosa que vamos a hacer es crear una malla de nuestro ManualObject. Las mallas son más sofisticadas que usar un ManualObject directamente para renderizar. Para hacer esto, simplemente necesitamos llamar a ManualObject::convertToMesh con el nombre de la malla guardada:
mo.convertToMesh("GrassBladesMesh");
hemos terminado de crear la malla de hierba. Nota que si has creado una malla altamente compleja de este modo, puedes salvarla a un archivo y simplemente cargarla desde él, en vez de recrear el ManualObject cada vez que estas cargando el programa. Para hacer esto, tomaremos el valor de retorno de convertToMesh (lo que descartamos en el código actual) , y alimentamos con lo devuelto a la función MeshSerializer::exportMesh. Aqui esta un ejemplo de como hacer esto:
// No anyadir esto al código!
MeshPtr ptr = mo.convertToMesh("GrassBladesMesh");
MeshSerializer ser;
ser.exportMesh(ptr.getPointer(), "grass.mesh");
Ahora estamos listos para crear la StaticGeometry.

Añadiendo la Geometría estática


El método createScene esta listo para llenarse con varias cosas. tengo que añadir el código para crear un suelo plano, un robot, y establecer la posición de la Cámara, y demas, ya hemos cubierto como hacer estas cosas en tutoriales anteriores. Asegúrate de que comprendes los contenidos actuales de esta función antes de continuar.

La primera cosa que vamos a hacer ahora es crear una Entity basada en la malla de hierba que hemos creado anteriormente y crear un objeto de StaticGeometry. Nota que sólo crearemos una Entity para usarla con nuestro StaticGeometry. Busca el método createScene y añade el siguiente código al final de él:
Ogre::Entity *grass = mSceneMgr->createEntity("grass", "GrassBladesMesh");
Ogre::StaticGeometry *sg = mSceneMgr->createStaticGeometry("GrassArea");
const int size = 375;
const int amount = 20;
La variable size definirá como de largo es el área que estamos cubriendo con la hierba y la variable amount definirá cuantos objetos pondremos en cada fila de nuestra StaticGeometry.

A continuacion la siguiente cosa que necesitamos hacer es definir el size (tamaño) y el origin (origen) de la StaticGeometry. Una vez que hemos construido el objeto (llamado a StaticGeometry::build), no podemos cambiar el origen o region definidas en StaticGeometry. El origen es la esquina izquierda que define la clase StaticGeometry. Si quieres colocar el StaticGeometry alrededor de un punto, necesitarás establecer el origen de coordenadas de x y z a la mitad del tamaño de la región para x, z:
sg->setRegionDimensions(Ogre::Vector3(size, size, size));
sg->setOrigin(Ogre::Vector3(-size/2, 0, -size/2));
Esto centra el objeto alrededor del punto (0,0,0). Para centrarlo en un punto del espacio 3D, necesitas hacer algo similar a esto:
<span style="font-family: Arial,sans-serif; font-style: normal; font-weight: normal; margin-bottom: 0cm;">// No anyadir al proyecto! </span>
<span style="font-family: Arial,sans-serif; font-style: normal; font-weight: normal; margin-bottom: 0cm;">sg->setOrigin(Vector3(-size/2, -size/2, -size/2) + Vector3(x, y, z)); </span>
Donde x, y, z es el punto en el espacion 3D centrado. Tambien notar que definimos la altura vertical del objeto cuando ajustamos la región. Asegúrate de que la componente y de setRegionDimensions es al menos tan larga como el mayor de los objetos en la StaticGeometry.

La siguiente cosa que tenemos que hacer es añadir objetos a la StaticGeometry. Este trozo de código es tan complejo porque estamos añadiendo una rejilla entera de hierba a la geometría, y dando un aumento en la posición x,z, una posición aleatoria, y una escala vertical aleatoria. En realidad, la cosa más importante de comprender en esto es la StaticGeometry::addEntity:
for (int x = -size/2; x < size/2; x += (size/amount))
{
 for (int z = -size/2; z < size/2; z += (size/amount))
 {
 Ogre::Real r = size / (float)amount / 2;
 Ogre::Vector3 pos(x + Ogre::Math::RangeRandom(-r, r), 0, z + Ogre::Math::RangeRandom(-r, r));
 Ogre::Vector3 scale(1, Ogre::Math::RangeRandom(0.9, 1.1), 1);
 Ogre::Quaternion orientation;
 
 orientation.FromAngleAxis(Ogre::Degree(Ogre::Math::RangeRandom(0, 359)), Ogre::Vector3::UNIT_Y);
 sg->addEntity(grass, pos, orientation, scale);
 }
}
La función addEntity coge en la Entity en uso, la posición del objeto, la orientación del objeto, y la escala del objeto. Cuando estas definiendo StaticGeometry deberás usar la función addEntity o la función addSceneNode. La función addSceneNode anda por -SceneNode añadiendo todas las Entities a la geometria estática, usando la posición, orientación, y la escala de los SceneNodes hijos en vez de especificarlos manualmente. Nota que si usas la función addSceneNode, asegúrate de eliminar el nodo desde su padre - SceneNode, ya que la función addSceneNode no lo elimina por ti. Si no lo haces, Ogre renderizar ambos el StaticGeometry que creaste y el original.

Finalmente necesitas construir el StaticGeometry antes de que sea mostrado:
sg->build();
Compila y ejecuta tu aplicación, deberías ver ahora un robot en un pequeño trozo de hierba.

Conclusión


Modificando el Objeto StaticGeometry


Una vez que el StaticGeometry se ha creado. Puedes, sin embargo, hacer cosas como ondas de hierba con el viento. Si estas interesado en como hacer esto, hecha un vistazo al demo de hierba que viene con Ogre. La función GrassListener::waveGrass modifica la hierba para realizar un movimiento como de onda.

Lotes en Objetos Avanzados


Esto es, claro, el comienzo de los lotes en objetos. Deberías usar StaticGeometry todo el tiempo cuando tengas objetos que estan agrupados juntos y no se moveran. Si estas intentando crear algo como un bosque intensivo o expansivo o intentas añadir hierba a un terreno muy grande, deberías echarle un vistazo a una de las técnicas más avanzadas de lotes, como es el motor PagedGeometry.

Siguiente, Tutorial Intermedio 6 Decals Proyectivos