martes, 17 de junio de 2008

Tercer tutorial: el HolaMundo más simple de Irrlicht (3ª parte)

Finalizamos esta primera ronda de mini-tutoriales alcanzando al fin el tutorial 1 de Irrlicht.
Lo visto hasta ahora nos servirá de base para los próximos tutoriales en los que iremos viendo como dotar de simulación física nuestra aplicación mediante el motor ODE, y otros conceptos más avanzados de Irrlicht a medida que los vamos aprendiendo.




#include <irrlicht/irrlicht.h>

using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace gui;

int main()
{
/** INICIALIZACION **/

IrrlichtDevice *device = createDevice(
video::EDT_OPENGL,
dimension2d(640, 480), 16,
false, false, false, NULL );

IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();


device->setWindowCaption(L"Hola Mundo - Tutorial 3");

guienv->addStaticText(L"Esto es texto fijo insertado en el GUI de la aplicacion",
rect(10,10,260,22), true);

/** CREACION **/

ICameraSceneNode* cameraNode = smgr->addCameraSceneNodeFPS( NULL, 100.0f, 200.0f );
IAnimatedMesh* dwarfMesh = smgr->getMesh( "res/dwarf.x" );
IAnimatedMesh* faerieMesh = smgr->getMesh( "res/faerie.md2" );
IAnimatedMeshSceneNode* dwarfNode = smgr->addAnimatedMeshSceneNode( dwarfMesh );
IAnimatedMeshSceneNode* faerieNode = smgr->addAnimatedMeshSceneNode( faerieMesh );
dwarfMesh->drop();
faerieMesh->drop();

/** ACTUALIZACION **/

dwarfNode->setScale( vector3d(.5f,.5f,.5f) );
dwarfNode->setPosition( vector3df(25,0,0) );
dwarfNode->setMaterialFlag( EMF_LIGHTING, false );
faerieNode->setPosition( vector3df(-25,15,0) );
faerieNode->setMaterialFlag( EMF_LIGHTING, false );
faerieNode->setMD2Animation ( scene::EMAT_STAND );
faerieNode->setMaterialTexture( 0, driver->getTexture("res/faerie.bmp") );
cameraNode->setPosition( vector3df(40,30,-50) );
cameraNode->setTarget( vector3df(0,5,0) );

/** EJECUCION **/

while( device->run() )
{
driver->beginScene( true, true, SColor(255,100,101,140) );
smgr->drawAll();
guienv->drawAll();
driver->endScene();
}

/** FINALIZACION **/

device->drop();
return 0;
}


Primera novedad: un nuevo objeto extraído del dispositivo Irrlicht principal: GUIEnvironment. Hasta ahora habíamos visto Irrlicht únicamente como un motor gráfico 3D, pero nada más lejos de la realidad. Nuestro engine favorito nos ofrece toda una colección de componentes para implementar interfaces de usuario 2D integradas perfectamente con el entorno tridimensional. Al igual que el SceneManager lo utilizamos para gestionar la escena 3D, disponemos del GUIEnvironment para gestionar los widgets (como les llaman otros APIs) de nuestra aplicación. Tenemos a nuestra disposición: button, checkbox, menu, file dialog, tab, window, scrollbar, etc. Como si de una aplicación de escritorio se tratara, ideal para diseñar nuestros menús en la propia aplicación. Como curiosidad, hay incluso quién ha utilizado Irrlicht exclusivamente como librería GUI para desarrollar alguna aplicación de escritorio.

Un sencillo ejemplo lo tenemos en las instrucciones siguientes, en el que añadimos un mensaje de texto fijo y visible sobre nuestra escena. Además de la cadena de caracteres, se le especifica el tamaño y posición del área rectangular del texto a través del objeto rect. El booleano es para indicar si queremos el área con o sin borde. (Si es la primera vez que ves esa "L" delante de una cadena de caracteres, busca información sobre el uso de wide chars en C/C++)

También le hemos cambiado el título a la ventana a través del device.

En este tutorial hemos sustituido la caja por modelos más complejos como los que utilizaríamos en una aplicación real. Realizamos la carga como ya vimos, con la diferencia de que ahora utilizamos un puntero de tipo "AnimatedMesh" ya que sabemos de antemano que los modelos que vamos a utilizar están animados. Luego los insertamos en la escena también como nodos de malla animada. Y por último liberamos las mallas.

Los modelos utilizados, además de ser más complejos poligonalmente están texturizados. En el modelo "dwarf", el propio formato DirecX (.x) incluye una referencia a la textura utilizada e Irrlicht la carga y aplica automáticamente. En cambio, el modelo "faerie" que está en formato MD2(Quake2) y éste no incluye la referencia a la textura por lo que debemos cargarla y asignarla nosotros manualmente. De esta forma, utilizando el VideoDriver podemos cargar imágenes y transformarlas al formato interno: Texture. Esta nueva textura puede ser aplicada al modelo a través del método "setMaterialTexture" de su nodo.
Como algo excepcional, el formato MD2 contiene un conjunto de animaciones pre-definidas propias del Quake2 (éstas son acciones del personaje: andar, disparar, agacharse, saltar, morir, etc.). Y es posible seleccionar rápidamente una de ellas utilizando el método específico del formato "setMD2Animation" pasándole alguna de las constantes: irr::scene::EMAT_* (Enum Md2 Animation Type).
Por último, como en este tutorial no hemos llegado a tocar el tema de las luces, el material de los modelos (su textura aplicada en este tutorial) se renderizará muy oscuro a causa de la ausencia de iluminación. Una solución rápida es desactivar el efecto de la iluminación sobre el material, y eso se hace a través del conjunto de flags de un material: setMaterialFlag( irr::video::EMF_*, true|false ). Curiosea sobre el conjunto de flags para ver de cuentas maneras puedes modificar el acabado de un material.

Ya solo resta añadir en el bucle de ejecución la pintada de los elementos de interfaces de usuario, y es tan fácil como decirle "drawAll" al GUIEnvironment.
Pon especial atención al orden de los "drawAll" del GUI y del SceneManager, ya que si invirtiéramos su orden estaríamos pintando los modelos encima del mensaje de texto, y a menos que sea el efecto que buscamos provocará extraños resultados.

Sigue leyendo >

lunes, 16 de junio de 2008

Segundo tutorial: el HolaMundo más simple de Irrlicht (2ª parte)

Seguimos con el segundo tutorial ampliando ligeramente la versión del HolaMundo realizada en la primera parte, sin correr demasiado para ir asimilando bien las bases de Irrlicht.


De nuevo, los fuentes y el binario ya compilado están en el box: tutorial_02.zip.

El código sin comentarios quedaría tal que así:

#include <irrlicht/irrlicht.h>

using namespace irr;

using namespace core;
using namespace scene;
using namespace video;

int main()
{
/** INICIALIZACION **/

IrrlichtDevice *device = createDevice(
video::EDT_OPENGL, dimension2d(640, 480), 16,
false, false, false, NULL );

IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();

/** CREACION **/

ICameraSceneNode* cameraNode = smgr->addCameraSceneNodeFPS( 0, 100.0f, 200.0f );

IMesh* boxMesh = smgr->getMesh( "res/box.x" );
IMeshSceneNode* boxNode = smgr->addMeshSceneNode( boxMesh );
boxMesh->drop();

/** ACTUALIZACION **/

boxNode->setScale( vector3d(10.0f,10.0f,10.0f) );
cameraNode->setPosition( vector3df(20,30,-40) );
cameraNode->setTarget( vector3df(0,5,0) );

/** EJECUCION **/

while( device->run() )
{
driver->beginScene( true, true, SColor(255,100,101,140) );
smgr->drawAll();
driver->endScene();
}

/** FINALIZACION **/

device->drop();
return 0;
}


Un primer cambio es apreciable en la cámara. El SceneManager de Irrlicht contiene un par de cámaras más sofisticadas que la utilizada en el primer tutorial. Estas cámaras destacan principalmente por venir pre-configuradas con eventos de teclado y ratón asociadas a ellas permitiendo manipularlas de forma intuitiva sin ninguna línea de código adicional. Las cámaras son llamadas "FPS" y "Maya". Si eres un jugón o te gusta el diseño gráfico ya te estarás imaginando el tipo de cámaras de las que hablamos.
La cámara FPS imita la configuración de la cámara típica de un Shooter (First Person Shooter) asociando los cursores a un movimiento de traslación horizontal y el ratón a la rotación de la cámara. Eso si, no esperes ningún tipo de colisión por defecto, ni siquiera con el plano horizontal XZ (el "suelo"), imagina más bien la configuración de cámara de un observador del shooter que está en "modo vuelo".
La cámara de tipo Maya tiene una configuración típica de un editor 3D, en el que la pulsación de los distintos botones del ratón y el movimiento del mismo activan las distintas opciones de: traslación, rotación y escalado/zoom de la cámara.
Ambas cámaras aceptan los mismos parámetros, y que interesen hasta el momento son el segundo y el tercero: velocidad de rotación y velocidad de traslación respectivamente.

Pero el verdadero cambio llega en las siguientes instrucciones. En este tutorial no vamos a pintar nosotros la caja explícitamente, en lugar de eso hemos abierto el Blender y la caja que aparece por defecto ha sido exportada a un formato que entienda Irrlicht, como por ejemplo DirectX (ficheros .x en texto plano). En cualquier aplicación gráfica que hagamos vamos a echar mano de modelos pre-diseñados, así que es el momento de enfocar el tutorial en ellos. El primer paso es realizar la carga del modelo y para ello se utiliza el método getMesh del SceneManager que es el encargado de generar una malla de Irrlicht a partir de un objeto almacenado en un archivo. Como se puede observar, tan solo se le proporciona como parámetro el modelo deseado, e Irrlicht internamente utiliza el cargador específico del formato en función de la extensión del fichero. Si le pasáramos una extensión que desconoce o sufriera algún tipo de error en la carga nos los indicará por la salida de consola.
Con nuestro modelo en forma de "Mesh", es el momento de añadirlo a nuestra escena de la misma forma que añadimos la cámara a traves del SceneManager. Hecho esto, la malla ya no la necesitamos y podemos hacer "drop" sobre ella. A partir de ahora, nuestra caja está encapsulada en un objeto llamado "MeshSceneNode" que nos va a proporcionar métodos para su manipulación, lo cual va a ser una gran ventaja comparado al tutorial anterior en el que la caja no la teníamos empaquetada de ninguna forma y nos dedicábamos a re-crearla en tiempo de render. ¿Porqué eso de "SceneNode"? El concepto de "nodo" en Irrlicht es la piedra angular de todo el engine. Ya comentábamos que el elemento SceneManager era la raíz de toda nuestra escena, pues bien, esto hay que tomárselo al pie de la letra ya que la escena está internamente representada en forma de árbol lógico con lo que nuestro SceneManager es literalmente la raíz del árbol. Partiendo de esta idea, es obvio entender como todo aquello que sea añadido a la escena es de forma genérica un nodo o "SceneNode". Iremos poco a poco descubriendo distintas implementaciones de nodos que extienden el SceneNode genérico para tratar con objetos más específicos, como por ejemplo los vistos hasta ahora: CameraSceneNode y MeshSceneNode.

En las últimas nuevas líneas de este tutorial podemos observar como realizar operaciones simples sobre nuestros objetos puestos en escena (la cámara y la caja). A través del puntero al nodo que hemos conservado se ha ajustado el escalado de la caja y la posición y enfoque de la cámara. Y lo mejor de todo, la tarea de render es poco más que un "drawAll" de nuestro gestor de escena.

Para concluir el tutorial, comentar que lo que ha sucedido internamente (respecto al tutorial 1) es que el pintado de la caja ha sido abstraído dentro del SceneNode, ya que es uno de sus método el encargado de acceder al VideoDriver para pintar la caja de forma similar a como lo hicimos en el tutorial anterior, y éste método es invocado automáticamente a través del "drawAll". Con lo cual, si estas pensando en hacer pintadas de forma manual a bajo nivel utilizando directamente el VideoDriver, te interesa implementarte tu propio SceneNode: tan sencillo como heredar de la interfaz "ISceneNode" e implementar sus métodos. Así integrarás limpiamente tu aplicación con Irrlicht.

Sigue leyendo >

sábado, 14 de junio de 2008

Primer tutorial: el HolaMundo más simple de Irrlicht

Nos ponemos las pilas comenzando con una colección de tutoriales que partan desde lo más básico. Empezamos obviamente con Irrlicht, con un HolaMundo más simple si es posible que el tutorial HelloWorld que acompaña al engine.


Antes que nada, el código y demás está disponible en el box de nuestro blog bajo el nombre de "tutorial_01". Su puesta en marcha debería ser tan sencilla como el tener instalado MinGW en el sistema, y ejecutar make sobre el tutorial, ya que han sido incluidas las cabeceras y librerías de Irrlicht necesarias para funcionar, haciendo así el código más portable y sencillo de poner en marcha al reducir los pre-requisitos del sistema en el que vaya a ser compilado/ejecutado.
Queda pendiente añadirle las librerías de linux y hacerlo así multiplataforma, por ahora sólo incluye lo necesario para ponerlo en marcha bajo MsWindows con MinGW.

Este HolaMundo ha sido pensado para aquellos que vienen (o venimos) de librerías gráficas de bajo nivel como OpenGL o DirectX y estamos acostumbrados a realizar el pintado por pantalla casi de forma manual, pensando siempre en vértices, aristas, normales, etc.. Así que apartaremos de nuestra vista aquellos objetos de Irrlicht más avanzados intentando dejar el mínimo número posible de objetos necesarios.

Empecemos echándole un ojo al código, y luego lo comentamos.
(Se han eliminado los comentarios que el zip del tutorial sí incluye)

#include <irrlicht/irrlicht.h>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;

int main()
{
IrrlichtDevice *device = createDevice(
video::EDT_OPENGL,
dimension2d(640, 480), 16,
false, false, false, NULL );

IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();

smgr->addCameraSceneNode( NULL, vector3df(20,30,-40), vector3df(0,5,0) );

while( device->run() )
{
driver->beginScene( true, true, SColor(255,100,101,140) );
smgr->drawAll();
driver->draw3DBox( aabbox3df(0.f,0.f,0.f, 10.f,10.f,10.f) );
driver->endScene();
}

device->drop();
return 0;
}


Lo primero que se observa es que para trabajar con Irrlicht tan solo hay que incluir una cabecera (que por cierto el blog por el momento no visualiza al confundirlo con una etiqueta HTML). Y lo mismo pasa a nivel de linkado, si observas el Makefile verás que tan solo ha habido que enlazar con una librería.

Todo el API de Irrlicht se encuentra bajo el espacio de nombres "irr". Y está estructurado mediante 5 sub-espacios de nombres más: core, scene, video, io, gui.

La primera instrucción que no puede faltar en una aplicación Irrlicht es una llamada a "irr::createDevice". Ésta función en la encargada de inicializar todo el motor gráfico, representado mediante el objeto "IrrlichtDevice" devuelto. Éste objeto es la madre del cordero de toda aplicación Irrlicht.
Vemos que en la incialización estamos definiendo la ventana de render a través del conjunto de parámetros que tiene la llamada:

- "deviceType": el primero de ellos nos permite seleccionar el motor de render utilizado en la aplicación. Irrlicht incluye una buena colección: OpenGL, DirectX8, DirectX9 y 2 implementaciones software propias. No hay excusas para no hacer aplicaciones multiplataforma! Los identificadores de los distintos motores los podemos encontrar en irr::video::EDT_*. "EDT" significa "Enum Device Type", y es muy común encontrarse este tipo de nomenclatura en el API, así que no lo olvides para encontrar por ti mismo aquello que estés buscando en un futuro.

- "windowSize": el tamaño de la ventana de render, como su nombre indica, expresado en pixels como suele ser costumbre. Aquí se espera un objeto irr::core::dimension2d que encapsula el par de valores.

- "bits": la profundidad de color en bits de la ventana. Típicamente "16" o "32". No olvidar que este valor sólo tiene efecto al trabajar a pantalla completa, ya que de otra forma se utiliza el valor con el que tengas configurado el escritorio.

- "fullscreen": un booleano que especifica si queremos la aplicacion a pantalla completa o no.

- "stencilbuffer": mediante un booleano indicamos si queremos hacer uso del "stencilbuffer" para sombras. Un concepto algo avanzado para el objetivo de este primer tutorial.

- "vsync": activamos o desactivamos la sincronización vertical de nuestra tarjeta gráfica. De nuevo solo funcionaría al trabajar a pantalla completa.

- "eventReceiver": un objeto de la clase "EventReceiver" encargado de gestionar todos los eventos que queremos capturar en nuestra aplicación. Ya llegaremos..


Tras crear nuestro dispositivo Irrlicht, extraemos de él dos de los objetos más importante que tiene instanciados. Como son usados frecuentemente, es habitual hacer con una referencia propia para no estar continuamente haciendo "device->getXXX()".
Los nombres de ambos objetos son bastante intuitivos: VideoDriver y SceneManager. A groso modo podríamos decir que VideoDriver trabaja a bajo nivel y SceneManager nos proporciona la capa más alta de abstracción de todo el motor gráfico.
Ésto mismo lo comprobamos con la siguiente instrucción en la que añadimos una cámara a nuestra escena mediante "addCameraSceneNode". Sus parámetros son la posición de la cámara y el punto al que mira, y para encapsular estos valores se utiliza la clase irr::core::vector3d.

El siguiente paso es entrar de lleno en el bucle de ejecución de nuestra aplicación gráfica. La condición del bucle puede ser tan sencilla como "device->run()". En este caso el cerrar la ventana o la aplicación detendrá el dispositivo irrlicht y provocará que finalice el bucle.
El bucle estará gobernado por dos instrucciones al más puro estilo OpenGL ó máquina de estados: "beginScene" y "endScene". El objetivo de "beginScene" es básicamente limpiar la pantalla, y para ello le pasamos el color con el que queremos que deje el fondo utilizando la clase irr::video::SColor( alpha, r,g,b ). Entre ambos métodos colocaremos todas aquellas instrucciones que tengan como objetivo pintar cosas en pantalla. Por ese motivo encontramos en este primer tutorial una llamada al drawAll del SceneManager, el cual se encarga el solito de pintar todo aquello que le hayamos añadido, en nuestro caso tan solo ha sido la cámara así que la "colocará" en escena.
A continuación probamos hacer una pintada nosotros mismos echando mano directamente del driver de video para pintar un cubo. Si curioseamos las llamadas drawXXX del videoDriver observaremos como son primitivas: triángulos, lineas, cajas/cubos, vértices, etc. El objeto utilizado para definir el cubo no es más que una clase que almacena 2 puntos (x,y,z, X,Y,Z). La nomenclatura "aabbox" viene del término utilizado habitualmente para referirse a los boundingbox: "aabb".
Finalmente tenemos la llamada a endScene que es la encargada realmente de hacer el render por pantalla, ya que antes estábamos pintando sobre un buffer distinto.

Por último, solo queda destruir el device en el momento de cerrar la aplicación, y en el API de Irrlicht todos los objetos siempre de liberan/destruyen mediante un método común llamado "drop".

Y hasta aquí el tutorial.
En el próximo nos acercaremos un poco más al HelloWorld "oficial" de Irrlicht para entender cual es la filosofía a seguir del motor para pintar las cosas, ya que veremos que la idea es no utilizar el driver directamente, que para algo existe la abstracción del SceneManager.

Sigue leyendo >

jueves, 5 de junio de 2008

Irrlicht 1.4.1

¡Irrlicht se actualiza! 5 meses después de la versión 1.4.0.
También lo ha hecho IrrEdit actualizándose con la nueva versión de Irrlicht.

Irrlicht 1.4.1 released

We just released Irrlicht 1.4.1, it is now available at the sourceforge servers. Happy downloading. Version 1.4.1 is mainly a bugfix release and includes:

* Improvements to the COLLADA 1.3 loader, support for Milkshape 1.8 files, Enhanced .3ds, .obj loaders, Microsoft .x file animation playback improvements
* Added X11 support on OSX
* Several speed optimizations
* Tons of bugfixes, API enhancements and other small improvments

Sigue leyendo >

lunes, 26 de mayo de 2008

Un ejemplo de IApplication

Con este artículo concluimos el bloque de la estructura de menus Irrlicht. Sin más preámbulos, vamos a ver el código y clases necesarias para tener una aplicación basica para nuestro proyecto.


Empezamos con el main, en la que instanciamos un objeto de tipo LSim.
#include <iostream>
#include "LSim.h"

using namespace std;

int main()
{
LSim *app = new LSim();
return app->mRun();
}


Yo no se vosotros, pero adoro los mains así de limpitos ;). La clase LSim es la encargada de instanciar el IrrlichtDevice. A su vez, instancia un objeto MainApplication (el menú principal) pasándole al constructor el device. A continuación vemos la declaracion de LSim.

class LSim
{
public:
LSim();
virtual ~LSim();
bool mRun();

protected methods:
bool mInit();
// bool Loop();

protected:
IrrlichtDevice *f_device;
MainApplication *mainApp;
};


Y su implementación. No pongo el constructor y destructor porque estan vacios.

bool
LSim::mRun()
{
if(!mInit()) return false;

mainApp = new MainApplication(f_device);
mainApp->mRun();
return true;
}

bool
LSim::mInit()
{
try
{
// create device and exit if creation failed
dimension2d<s32> resolucion(640,480);
bool fullScreen = false;
u32 bpp = 32;
f_device = createDevice(video::EDT_OPENGL, resolucion,bpp,fullScreen);
if (f_device == NULL) return false;
return true;
}
catch(std::exception &e)
{
std::cerr<<"Error: "<<e.what()<<std::endl;
}
return false;
}


Acabamos de ver cómo se llama a MainApplication. Veamos ahora la declaración de MainApplication.

class MainApplication : public IApplication
{
public:
MainApplication(IrrlichtDevice *device);
virtual ~MainApplication();
virtual bool mInit();
virtual bool mRemove();

//events
static void mButtonQuit_Click(void* o, const SEvent& event);
static void mButtonNewWindow_Click(void *o, const SEvent & event);
static void mButtonFileOpen_Click(void *o, const SEvent & event);
static void mScrollBar_Changed(void *o, const SEvent & event);

// static void mSubButton_Click(void *o, const SEvent & event);

static void mButtonAddLogo_Click(void* o, const SEvent& event);
static void mButtonRemoveLogo_Click(void *o, const SEvent & event);
static void mButtonLaunchEditor_Click(void *o, const SEvent & event);
static void mButtonLaunchSimulator_Click(void *o, const SEvent & event);

protected:
EditorApplication *f_editor;
SimulatorApplication *f_simulador;

//controls
IGUIContextMenu * f_menu;
IGUIButton *f_buttonQuit;
IGUIButton *f_buttonNewWindow;
IGUIButton *f_buttonFileOpen;
IGUIButton *f_buttonAddLogo;
IGUIButton *f_buttonRemoveLogo;
IGUIButton *f_buttonLaunchEditor;
IGUIButton *f_buttonLaunchSimulator;
IGUIStaticText *f_staticTextTransparentControl;
IGUIScrollBar *f_scrollbar;
IGUIStaticText *f_staticTextLogger;
IGUIListBox *f_listBoxLogger;
IGUISkin* f_skin;
IGUIFont* f_font;
IGUIImage* f_ImageLogo;
IGUIStaticText *f_staticTextFPS;
};


Observa como esta clase ya se asemeja a una formulario de M$Windows, con sus controles, eventos, etc. Además tiene dos objetos especialmente interesantes, el editor y el simulador, que también heredan de IApplication. De hecho, creo que tenia que haberlos declarado como IApplication, mea culpa. Veamos ahora la implementación. Sólo voy a poner los metodos importantes, mInit y mLoop, además de dos eventos importantes, los que lanzan al editor y al simulador respectivamente.

bool
MainApplication::mInit()
{
f_eventReceiver = new EventReceiver(this);
f_device->setEventReceiver(f_eventReceiver);

//Inicializacion de controles
f_menu = f_env->addMenu(0, f_genIDs->getNewID());
f_buttonQuit = f_env->addButton(rect<s32>(10,210,100,240), 0, f_genIDs->getNewID(), L"Quit");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonQuit->getID(), MainApplication::mButtonQuit_Click);
f_buttonNewWindow = f_env->addButton(rect<s32>(10,250,100,290), 0, f_genIDs->getNewID(), L"New Window");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonNewWindow->getID(), MainApplication::mButtonNewWindow_Click);
f_buttonFileOpen = f_env->addButton(rect<s32>(10,300,100,340), 0, f_genIDs->getNewID(), L"File Open");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonFileOpen->getID(), MainApplication::mButtonFileOpen_Click);

f_buttonAddLogo = f_env->addButton(rect<s32>(10,350,100,370), 0, f_genIDs->getNewID(), L"Add Logo");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonAddLogo->getID(), MainApplication::mButtonAddLogo_Click);
f_buttonRemoveLogo = f_env->addButton(rect<s32>(10,380,100,400), 0, f_genIDs->getNewID(), L"Remove Logo");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonRemoveLogo->getID(), MainApplication::mButtonRemoveLogo_Click);

f_buttonLaunchEditor = f_env->addButton(rect<s32>(10,410,100,430), 0, f_genIDs->getNewID(), L"Editor");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonLaunchEditor->getID(), MainApplication::mButtonLaunchEditor_Click);
f_buttonLaunchSimulator = f_env->addButton(rect<s32>(10,440,100,460), 0, f_genIDs->getNewID(), L"Simulator");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonLaunchSimulator->getID(), MainApplication::mButtonLaunchSimulator_Click);

f_staticTextTransparentControl = f_env->addStaticText(L"Transparent Control:", rect<s32>(150,20,350,40), true,true,0,f_genIDs->getNewID(),false);

f_scrollbar = f_env->addScrollBar(true,rect<s32>(150, 45, 350, 60), 0, f_genIDs->getNewID());
f_scrollbar->setMax(255);
f_scrollbar->setPos(127);
f_eventReceiver->mAddGuiEvent(EGET_SCROLL_BAR_CHANGED, f_scrollbar->getID(), MainApplication::mScrollBar_Changed);
f_staticTextLogger = f_env->addStaticText(L"Logging ListBox:", rect<s32>(50,80,250,100), true,true,0,f_genIDs->getNewID());

f_listBoxLogger = f_env->addListBox(rect<s32>(50, 110, 250, 180),0,f_genIDs->getNewID());

f_skin = f_env->getSkin();
f_font = f_env->getFont("media/fonthaettenschweiler.bmp");
if (f_font) f_skin->setFont(f_font);
f_ImageLogo= f_env->addImage(
f_driver->getTexture("media/irrlichtlogo.png"),
position2d<int>(f_driver->getScreenSize().Width - 128,f_driver->getScreenSize().Height- 105));

//Anyadimos el texto de los fps
f_staticTextFPS = f_env->addStaticText(L"", core::rect<s32>(10,10,100,26), true, false, 0,f_genIDs->getNewID());

// IGUIWindow* subventana = f_env->addWindow(rect<s32>(0,0,800,600), true,L"titulo de la subventana",0,2000);
// IGUIButton *subboton = f_env->addButton(rect<s32>(50,50,50+100,50+32), subventana, 2001, L"Quit");
return true;
}

bool
MainApplication::mRemove()
{
f_menu->remove();
f_buttonLaunchSimulator->remove();
f_buttonLaunchEditor->remove();
f_buttonQuit->remove();
f_buttonNewWindow->remove();
f_buttonFileOpen->remove();
f_buttonAddLogo->remove();
f_buttonRemoveLogo->remove();
f_staticTextTransparentControl->remove();
f_scrollbar->remove();
f_staticTextLogger->remove();
f_listBoxLogger->remove();
if(f_ImageLogo) f_ImageLogo->remove();
f_staticTextFPS->remove();
return true;
}

void
MainApplication::mButtonLaunchEditor_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->mRemove();
me->f_editor = new EditorApplication(me->f_device);
me->f_editor->mRun();
me->mInit();
}

void
MainApplication::mButtonLaunchSimulator_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->mRemove();
me->f_simulador = new SimulatorApplication(me->f_device);
me->f_simulador->mRun();
me->mInit();
}

El método mInit es el encargado de inicializar los controles y configurar a f_eventReceiver. Después de él, viene mRemove, encargado de retirar todos los controles y de asear el IrrlichtDevice. Esto lo hacemos así porque no podemos eliminar ni reasignar el objeto IGUIEnvironment del IrrlichtDevice, con lo que cada objeto IAplicación que recibe el IrrlichtDevice, pone sus controles y eventos y al terminar los quita del mismo.

Fijaos en los eventos que lanzan al editor y al simulador. Antes de instanciar, por ejemplo, el editor, se invoca a mRemove de MainApplication, para que al instanciar el editor, éste reciba el IrrlichrtDevice limpio y aseado. Tambien puede ocurrir que no sea necesario quitar todos los controles o incluso mantener algunos.

El EditorApplication y SimulatorApplication son parecidos a MainApplication, salvo que cada uno tendrá sus controles y funcionalidad.

Bueno, con esto hemos terminado el bloque de Estructura de menus Irrlicht. Conforme he ido escribiendo estas dos últimas entradas me he ido dando cuenta de algunos defetos de diseño, como el tema de IApplication como interfaz pura y otras interfaces que se podrían implementar. Será cuestion de corregislas.

Se me olvidaba deciros que en la cajita que tenemos de Box.Net hay un ejemplo que pusimos al principio de tener el blog. si lo pruebas podrás ver el menu principal y te permitira pasar al simulador o al editor. en el simulador presiona la tecla 'c' para liberar el puntero del raton y poder pulsar en el boton para volver al menú principal.

Un saludo.

Sigue leyendo >

La clase interfaz de ventana IApplication

Seguimos hoy con el bloque que trata de la estructura de ventanas con Irrlicht. Ahora nos toca ver la interfaz que nos permitirá implementar las distintas secciones de la aplicación.


Veamos la declaración de la clase.
#include <irrlicht/irrlicht.h>
#include "EventReceiver.h"
#include "GeneraID.h"
#include "FPSManager.h"

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

class IApplication
{
public:
IApplication(IrrlichtDevice *device);
virtual ~IApplication();
virtual bool mRun();
virtual bool mInit() = 0;
virtual bool mRemove() = 0;
virtual bool mLoop();
bool getRunLoop();
void setRunLoop(bool value);

protected:
virtual bool mPreDraw();

protected:
IrrlichtDevice *f_device;
EventReceiver *f_eventReceiver;
GeneraID *f_genIDs;
IVideoDriver *f_driver;
IGUIEnvironment *f_env;
ISceneManager *f_smgr;

bool f_runLoop;
IFPSManager *f_FPSManager;
};


He de decir que me equivoqué con el nombre, no tenía que haberse llamado IApplication, puesto que no es una interfaz pura, tiene propiedades y metodos implementados (lo siento por los puristas del diseño ;)

En su parte privada vemos que tiene punteros a objetos que ya comentamos, como el IrrlichtDevice, EventReceiver, IFPSManager; otros de Irrlicht como IVideoDriver, IGUIEnvironment e ISceneManager y uno de cosecha propia, el GeneraID.
Los objetos de irrlicht vereis que son extraidos del IrrlichtDevice para usarlos con mayor comodidad.

En cuanto a GeneraID, vereis que es necesario para, como su nombre indica, generar identificadores únicos para los controles del GUI. Recordais el ejemplo típico del EventReceiver de los tutoriales? También hay que destacar que para que los id's sean verdaderamente únicos, solo puede haber una instancia de la clase. Esto se consigue con el patron de diseño singleton. He aqui su declaración:
#include <iostream>
#include <irrlicht/irrlicht.h>

using namespace irr;

class GeneraID
{
public:
static GeneraID* getInstance();
s32 getNewID();
s32 getLastID();
virtual ~GeneraID();

protected:
GeneraID(s32 initial = 0);
s32 f_id;
static GeneraID* s_instancia;
};

Y su implementación.
#include"GeneraID.h"
GeneraID* GeneraID::s_instancia = 0;

using namespace irr;

GeneraID*
GeneraID::getInstance()
{
if(!s_instancia) s_instancia = new GeneraID();
return s_instancia;
}

GeneraID::GeneraID(s32 initial)
{
f_id = initial >= 0 ? initial : 0;
}

GeneraID::~GeneraID()
{
}

s32
GeneraID::getNewID()
{
return ++f_id;
}

s32
GeneraID::getLastID()
{
return f_id;
}


volviendo a IApplication, en la parte pública, tenemos métodos implementados como mRun, mLoop, y los métodos get y set del atributo f_runLoop. mRun y mLoop nos permiten ejecutar el bucle de ejecución de la aplicación y mediante f_runLoop, podemos parar la ejecución del mismo.

Veamos ahora la implemetación de IApplication.
#include "IApplication.h"

IApplication::IApplication(IrrlichtDevice *device)
{
f_device = device;
f_driver = f_device->getVideoDriver();
f_env = f_device->getGUIEnvironment();
f_smgr = f_device->getSceneManager();
f_genIDs = GeneraID::getInstance();
f_eventReceiver = NULL;
f_runLoop = true;
f_FPSManager = new FPSManager(f_device);
}

IApplication::~IApplication()
{
f_device = NULL;
f_driver = NULL;
f_env = NULL;
f_smgr = NULL;
f_genIDs = NULL;
f_runLoop = false;
if(f_FPSManager != NULL)
delete f_FPSManager;
f_FPSManager = NULL;
}

bool
IApplication::mRun()
{
if(!mInit()) return false;
if(!mLoop()) return false;
if(!mRemove()) return false;
return true;
}

bool
IApplication::mLoop()
{
while(f_runLoop && f_device->run() && f_driver)
{
f_FPSManager->mUpdateFPS();
if (f_device->isWindowActive())
{
f_FPSManager->mDrawFPS();
mPreDraw();

f_driver->beginScene(true, true, SColor(0,122,65,171));
f_smgr->drawAll();
f_env->drawAll();
f_driver->endScene();
}
else
f_device->yield();
}

//f_device->drop();
return true;
}

bool
IApplication::getRunLoop()
{
return f_runLoop;
}

void
IApplication::setRunLoop(bool value)
{
f_runLoop = value;
}

bool
IApplication::mPreDraw()
{
return true;
}


Podemos observar que el contructor no tiene nada especial, tan solo que recibe el IrrlichtDevice y recupera de él, el videoDriver, GUIEnvironment y SceneManager.

El metodo mLoop es el interesante. Pese a que es un bucle típico de los tutoriales de Irrlicht, le hemos agregado un par de cosillas, tales como la gestion de los FPS y en método mPreDraw(), que lo implementarán las clases sucesoras si hiciese falta. mPreDraw() surgió como necesidad en el simulador, que necesita procesar instucciones ODE antes de dibujar en ventana.

En la próxima entrega veremos ejemplos de uso de IApplication con el menú principal, el editor y el simulador.

Sigue leyendo >

Controlando la tasa de frames por segundo

En esta entrada explicaremos cómo controlar la cantidad de frames por segundo que tendrá nuestra aplicación Irrlicht.


Se que más de uno se preguntará ¿por qué limitar los FPS? Cuantos mas mejor ¿no? La respuesta es sí y no. Veréis, a partir de los 30fps ya no notamos diferencia de fluidez, luego, no es necesario que la cpu y gpu curren al 100%. Si limitamos la tasa de frames a 30, evitaremos el recalentamiento de los chips y eso, en portatiles se agradece mucho: menos consumo de bateria (si fuera el caso), menos calor, menos movimiento de ventilador y por tanto, menos acumulación de polvo, etc.

Asi pues, nuestra clase aplicacion tendrá un objeto que sea el encargado de introducir pausas limitar los fps. En caso de que el equipo no llegue al limite, no se introducirían pausas.

Para hacerlo bonito, lo haremos al estilo IS1. Implementaremos una interfaz que tendrá los métodos minimos y necesarios para que cualquier clase que implemente dicha interfaz, pueda controlar sin problemas los fps.

He aquí la clase interfaz IFPSManager. Vemos que tiene dos metodos a implementar por las clases que hereden de ésta, mUpdateFPS() y mDrawFPS().

class IFPSManager
{
public:
IFPSManager();
IFPSManager(IrrlichtDevice *device);
virtual ~IFPSManager();

virtual void mUpdateFPS() = 0;
virtual void mDrawFPS() = 0;
};



Y ahora la declaración de la clase FPSManager que implementa la interfaz.
#include "IFPSManager.h"

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

class FPSManager : public IFPSManager
{
public:
FPSManager();
FPSManager(IrrlichtDevice *device);
virtual ~FPSManager();

virtual void mUpdateFPS();
virtual void mDrawFPS();

protected:
IrrlichtDevice *f_device;
IVideoDriver *f_driver;

s32 fFPSMax;
f32 fFPSMaxMiliSeconds;
f32 fPauseInDraw;
};



No nos olvidemos de la implementación. La forma de calcular la pausa entre frames aún no es definitiva, pero gracias a la interfaz, podemos escribir otro gestor de fps sin tener que desechar este FPSManager.
#include "FPSManager.h"

FPSManager::FPSManager()
{
f_device = NULL;
f_driver = NULL;
fFPSMax = 0;
fFPSMaxMiliSeconds = 0.0;
fPauseInDraw = 0.0;
}

FPSManager::FPSManager(IrrlichtDevice *device)
{
f_device = device;
f_driver = f_device->getVideoDriver();
fFPSMax = 25;
fFPSMaxMiliSeconds = 1/(f32)fFPSMax;
fPauseInDraw = 0.0;
}

FPSManager::~FPSManager()
{
f_device = NULL;
fFPSMax = 0;
fFPSMaxMiliSeconds = 0.0;
fPauseInDraw = 0.0;
}

void
FPSManager::mUpdateFPS()
{
if (f_driver->getFPS() > fFPSMax)
fPauseInDraw = (fFPSMaxMiliSeconds - 1.0/(f32)f_driver->getFPS()) * 1000.0;
f_device->sleep((u32)fPauseInDraw);
}

void
FPSManager::mDrawFPS()
{
core::stringw str(core::stringw(f_driver->getFPS()));
str.append(L"fps");
f_device->setWindowCaption(str.c_str());
}



Hasta aquí el gestor de frames por segundo. En la proxima entrega hablaremos de la Interfaz de ventana IApplication.



Sigue leyendo >