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.
lunes, 26 de mayo de 2008
Controlando la tasa de frames por segundo
domingo, 25 de mayo de 2008
Dividiendo la aplicación en ventanas
Buenas, en esta entrada vamos a empezar a explicar la forma de implementar nuestra aplicación Irrlicht de modo que podamos dividirla en varias zonas o ventanas, en nuestro caso, un menú principal, el editor y el simulador.
Una forma de hacerlo sería instanciando y cerrando el IrrlichtDevice *device para "pasar" de una ventana a otra, pero no quedaría nada bien. Sería como si las aplicaciones de ventana habituales se cerraran y abrieran en vez de cambiar su contenido. Así que es precisamente eso lo que haremos, cambiar el contenido de la ventana principal.
En Irrlicht la "ventana" es un objeto de tipo IrrlichtDevice. Lo creamos invocando al metodo createDevice(). Una vez lo tengamos creado podemos modificar sus propiedades para agregar controles, cámaras, eventos, comportamiento, etc. A continuación vemos un ejemplo de inicialización del device de Irrlicht. 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;
La estrategia consiste en instanciar un IrrlichtDevice y encapsular en una clase todos los elementos necesarios para administrar los controles, sus eventos, bucle de ejecución, etc. A priori ya nos podemos imaginar que nuestra clase aplicación recibirá por el constructor el device ya instanciado como en el ejemplo siguiente. mainApp = new MainApplication(f_device);
mainApp->mRun();
En la proxima entrega veremos, antes de entrar en detalle con la clase de aplicación, cómo gestionar la tasa de frames por segundo.
Formateador de código fuente
Estos días que hemos estado poniendo codigo fuente en los post, he sufrido mucho el tema del formato, indentado etc. En formatmysourcecode.blogspot.com puedes dar formato, o convertir en código html tu código fuente. Solo le falta los colores de las pabras reservadas ^_^. Lástima que eso dependa del lenguaje, pero ya se nos ocurrirá algo.
Sigue leyendo >El nuevo EventReceiver
Terminamos hoy la parte del gestor de eventos exponiendo al detalle el nuevo EventeReceiver.
Para empezar, aquí está la declaración de la clase, que iría en su respectivo archivo de cabecera:#include <irrlicht/irrlicht.h>
#include <map>
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#define AppCB(var) void (*var)(void*, const SEvent&)
#define parGUIE std::pair< EGUI_EVENT_TYPE, int >
#define mapGUI std::map< parGUIE, AppCB() >
#define parKEYE std::pair< EKEY_CODE, bool >
#define mapKEY std::map< parKEYE, AppCB() >
#define mapMOUSE std::map< EMOUSE_INPUT_EVENT, AppCB() >
#define mapLOG std::map< ELOG_LEVEL, AppCB() >
//#define mapUSER AppCB()
class EventReceiver : public IEventReceiver
{
public:
EventReceiver(void* parent);
virtual ~EventReceiver();
virtual bool OnEvent(const SEvent& event);
bool mAddGuiEvent(EGUI_EVENT_TYPE eventType, int id, AppCB(callback));
bool mAddKeyUpEvent(EKEY_CODE code, AppCB(callback));
bool mAddKeyDownEvent(EKEY_CODE code, AppCB(callback));
bool mAddMouseEvent(EMOUSE_INPUT_EVENT input_event, AppCB(callback));
bool mAddLogEvent(ELOG_LEVEL level, AppCB(callback));
protected:
mapGUI f_guiEvents;
mapKEY f_keyEvents;
mapMOUSE f_mouseEvents;
mapLOG f_logEvents;
void* f_parent;
};
Bien, al principio vemos los includes necesarios y las definiciones, que ya explicamos, de las tablas hash y el puntero a función. Mas abajo, dentro de la declaración de la clase, que hereda de IEventReceiver, hemos añadido, además del onEvent, métodos para facilitar la adición de nuevos eventos, de modo que EventReceiver pueda gestionarlos adecuadamente. Cada uno de estos métodos recibe distintos en función del tipo de evento. Tal es el caso de mAddGuiEvent, que recibe el tipo de evento GUI y el id del control que lo provocó. Lo que sí que tienen en común, es el puntero a función o callback a invocar una vez producido el evento.
En la parte protegida podemos observar las tablas hash que contendrán los eventos y un puntero genérico (void*) al padre de la instancia de la clase. Este último campo lo veremos más abajo.
Veamos ahora la implementación. Primero, el contructor y destructor, nada de especial. Como las tablas hash no han sido creadas de manera dinámica, no es necesario ponelas en ninguno de los dos metodos.#include "EventReceiver.h"
EventReceiver::EventReceiver(void* parent)
{
f_parent = parent;
}
EventReceiver::~EventReceiver()
{
f_parent = NULL;
}
Ahora veamos los métodos que nos permiten añadir facilmente callbacks a los eventos. Tampoco tiene ningún secreto, dado que tan sólo agregan el callback a la tabla hash pertinente con la clave recibida.bool
EventReceiver::mAddGuiEvent(EGUI_EVENT_TYPE eventType, int id, AppCB(callback))
{
parGUIE key(eventType,id);
f_guiEvents[key] = callback;
return true;
}
bool
EventReceiver::mAddKeyUpEvent(EKEY_CODE code, AppCB(callback))
{
parKEYE key(code,false);
f_keyEvents[key] = callback;
return true;
}
bool
EventReceiver::mAddKeyDownEvent(EKEY_CODE code, AppCB(callback))
{
parKEYE key(code,true);
f_keyEvents[key] = callback;
return true;
}
bool
EventReceiver::mAddMouseEvent(EMOUSE_INPUT_EVENT input_event, AppCB(callback))
{
f_mouseEvents[input_event] = callback;
return true;
}
bool
EventReceiver::mAddLogEvent(ELOG_LEVEL level, AppCB(callback))
{
f_logEvents[level] = callback;
return true;
}
Y ahora sí, damas y caballeros, aquí viene el metodo onEvent, el cual tiene un switch que, según el evento principal, formará la clave y preguntará a la tabla hash correspondiente si tiene dicha clave, en cuyo caso recuperará el callback y lo invocará. Es aqui cuando hace uso del attributo f_parent. Recordad que el puntero a función debe apuntar a un metodo de clase (estático) y necesitábamos pasarle el objeto a manipular.bool
EventReceiver::OnEvent(const SEvent& event)
{
switch(event.EventType)
{
case EET_GUI_EVENT:
{
s32 id = event.GUIEvent.Caller->getID();
EGUI_EVENT_TYPE eventType = event.GUIEvent.EventType;
parGUIE key(eventType,id);
if(f_guiEvents.find(key) != f_guiEvents.end())
{
AppCB(callback) = f_guiEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_KEY_INPUT_EVENT:
{
EKEY_CODE code = event.KeyInput.Key;
bool pressed = event.KeyInput.PressedDown;
parKEYE key(code,pressed);
if(f_keyEvents.find(key) != f_keyEvents.end())
{
AppCB(callback) = f_keyEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_MOUSE_INPUT_EVENT:
{
EMOUSE_INPUT_EVENT key = event.MouseInput.Event;
if(f_mouseEvents.find(key) != f_mouseEvents.end())
{
AppCB(callback) = f_mouseEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_LOG_TEXT_EVENT:
{
ELOG_LEVEL key = event.LogEvent.Level;
if(f_logEvents.find(key) != f_logEvents.end())
{
AppCB(callback) = f_logEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_USER_EVENT:
default:
{
break;
}
};
return false;
}
Para terminar, un ejemplo de uso del nuevo EventReceiver. Aquí vemos un cacho de código en el que se inicializa una instancia de tipo EventReceiver y se le agregan callbacks. 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);
Y a continuación vemos la implementación de los callbacks añadidos en el codigo anterior. Veis como al principio de cada evento se hace una conversión (cast) al tipo MainApplication, que es el que hace de padre (el valor del atributo f_parent) de f_eventReceiver.void
MainApplication::mButtonQuit_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_device->closeDevice();
// me->f_ImageLogo->remove();
}
void
MainApplication::mButtonNewWindow_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_listBoxLogger->addItem(L"Window created");
IGUIWindow* window = me->f_env->addWindow(
rect<s32>(100, 100, 300, 200),
false, // modal?
L"Test window");
me->f_env->addStaticText(L"SubVentanica",
rect<s32>(35,35,140,50),
true, // border?,
false, // wordwrap?
window);
}
void
MainApplication::mButtonFileOpen_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_listBoxLogger->addItem(L"File open");
me->f_env->addFileOpenDialog(L"Please choose a file.");
}
Bien, mediante los punteros a función hemos conseguido hacer mas mantenible el objeto EventReceiver necesario para gestionar los eventos con Irrlicht. En la proxima entrega de esta serie, empezaremos a pincelar la estructura de las ventanas en Irrlicht.
Colecciones de punteros a funciones
Seguimos elaborando la estructura base de la aplicación y para ello, explicaremos hoy que colecciones de objetos utilizar.
En la entrada anterior vimos que tipos de eventos tiene Irrlicht. Haremos uso de contenedores template o plantilla de la conocida familia STL de C++.
La elección del contenedor
Analicemos la situación con detenimiento. Necesitamos un contenedor de objetos que sea eficiente tanto en la inserción como en la consulta de datos, y ademas, que esten indexados por una clave, p. ej. el tipo de evento. En mi opinión, la tabla hash o array asociativo es el mas indicado (si sabes de uno mejor, por favor, dímelo ;)
Qué vamos a almacenar
Está claro, que almacenaremos punteros a funciones, pero, ¿qué declaración van a tener? Necesitamos un puntero a función que nos valga para cualquier evento. No es que me quiera copiar de .Net y Java, pero creo que uno de los parametros de la función será el propio SEvent de Irrlicht. ¿Y el retorno? en principio void, dado que el evento no suele llamase directamente, puede que me equivoque, es más, no me extrañaría, aún no conocemos Irrlicht lo sifuciente.
Los punteros a funciones tienen un inconveniente. Y es que no se puede referencia a un método de instancia. Si queremos referenciar al método de una clase, éste debe ser estático, con lo que perdemos el puntero this y cualquier atributo o campo del objeto. Pero solventar este problema es facil. Basta con que el metodo estático reciba un parametro adicional, un puntero al Objeto de la propia clase o mejor, para evitar problemas de autoreferencia, un puntero a void. Dentro de la funcion solo hay que hacer un cast al objeto de la clase y podremos acceder a todos sus atributos y campos.
He aquí el puntero a función que usaremos:
void (*puntero_a_funcion)(void*, const SEvent&)
No se tú, pero yo me canso facilmente de escribir este chorizo en cada sitio que deba usarlo, asi que me creo un define como este:
#define AppCB(var) void (*var)(void*, const SEvent&)
El define anterior es una macro que recibe el nombre del puntero a funcion, para mayor comodidad, puesto que no siempre necesito escribirlo.
Qué clave usaremos para indexar
Como ya vimos, tenemos 5 tipos de eventos, aunque, de momento el tipo de evento de usuario no lo usamos (o aún no le hemos visto utilidad). Podríamos tener una sola tabla hash para albergar a todos los eventos, pero si particularizamos en 4 contenedores separados, podremos identificar mejor los eventos. Además, de este modo no tendremos problemas con los enumerados o defines.
Si nos fijamos en el tipo de la interfaz gráfica de usuario, SGUIEvent, vemos que tiene un segundo nivel de tipo de evento, mediante gui::EGUI_EVENT_TYPE. Así que para indexar los eventos del GUI, usaremos un Pair como clave, compuesta por el gui::EGUI_EVENT_TYPE y el id del control que provocó el evento.
#define parGUIE std::pair< EGUI_EVENT_TYPE, int >
Para las teclas también usaremos un Pair que contrendrá la tecla presionada y un booleano que indicará si ha sido presionada o dejada de presionar.
#define parKEYE std::pair< EKEY_CODE, bool >
En lo referente a los eventos de raton y log, nos basta con indexarlos por el SMouseInput y SLogEvent respectivamente.
Los contenedores
Tenemos ya, todo lo necesario para definir los contenedores:
#define mapGUI std::map< parGUIE, AppCB() >
#define mapKEY std::map< parKEYE, AppCB() >
#define mapMOUSE std::map< EMOUSE_INPUT_EVENT, AppCB() >
#define mapLOG std::map< ELOG_LEVEL, AppCB() >
En la proxima entrega veremos finalmente la nueva clase que usaremos en adelante en cualquier ventana, el nuevo EventReceiver.
sábado, 24 de mayo de 2008
Tipos de eventos y subtipos
En esta breve entrega analizaremos los distintos tipos de eventos que aporta Irrlicht
El método OnEvent recibe un objeto SEvent. Si miramos el api de irrlicht, veremos que hay 5 tipos de eventos:
A su vez, los eventos de controles (SGUIEvent) tiene sub tipos, para poder distinguir, por ejemplo, el click de un boton de FileOpenDialog. Es por ello que cada colección de eventos depende del tipo de evento.
En la proxima entrega veremos las colecciones a emplear.
Ejemplo típico de los tutoriales
He aquí un ejemplo de EventReceiver extraido de uno de los tutoriales.
En él se aprecia claramente que todos los eventos estan programados de manera estática y que añadir, eliminar o modificar, es una ardua tarea.class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
// Escape swaps Camera Input
if (event.EventType == EET_KEY_INPUT_EVENT &&
event.KeyInput.Key == irr::KEY_ESCAPE &&
event.KeyInput.PressedDown == false)
{
if ( Device )
{
scene::ICameraSceneNode * camera = Device->getSceneManager()->getActiveCamera ();
if ( camera )
{
camera->setInputReceiverEnabled ( !camera->isInputReceiverEnabled() );
}
return true;
}
}
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = Device->getGUIEnvironment();
switch(event.GUIEvent.EventType)
{
case EGET_FILE_SELECTED:
{
// load the model file, selected in the file open dialog
IGUIFileOpenDialog* dialog =
(IGUIFileOpenDialog*)event.GUIEvent.Caller;
loadModel(core::stringc(dialog->getFileName()).c_str());
}
case EGET_SCROLL_BAR_CHANGED:
// control skin transparency
if (id == 104)
{
s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
video::SColor col = env->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(pos);
env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}
break;
case EGET_BUTTON_CLICKED:
switch(id)
{
case 1101:
{
// set scale
gui::IGUIElement* root = env->getRootGUIElement();
core::vector3df scale;
core::stringc s;
s = root->getElementFromId(901, true)->getText();
scale.X = (f32)atof(s.c_str());
s = root->getElementFromId(902, true)->getText();
scale.Y = (f32)atof(s.c_str());
s = root->getElementFromId(903, true)->getText();
scale.Z = (f32)atof(s.c_str());
if (Model)
Model->setScale(scale);
}
break;
case 1102:
env->addFileOpenDialog(L"Please select a model file to open");
break;
case 1103:
showAboutText();
break;
case 1104:
createToolBox();
break;
case 1105:
env->addFileOpenDialog(L"Please select your game archive/directory");
break;
}
break;
}
}
return false;
}
};
Si nos fijamos en el subtipo de evento EGET_BUTTON_CLICKED vemos que distingue a cada boton por el id y con valores de enteros directamente. Imagínate que queramos añadir botones dinámicamente segun una configuracion u otra!!
En la proxima entrega examinaremos la jerarquía de eventos que tiene Irrlicht.